mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-06 03:00:23 +00:00
Compare commits
2 Commits
add-dp-rba
...
ashleyst/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eed4df8c67 | ||
|
|
3418ce93ec |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1 +0,0 @@
|
|||||||
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)
|
|
||||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -101,6 +101,12 @@ jobs:
|
|||||||
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
|
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
|
||||||
env:
|
env:
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
|
- name: Post a comment with the preview URL
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
run: gh pr comment $PR_NUMBER --body "Build complete. You can now [preview this branch in the portal](https://cosmos-explorer-preview.azurewebsites.net/pull/$PR_NUMBER?feature.someFeatureFlagYouMightNeed=true)"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
endtoendemulator:
|
endtoendemulator:
|
||||||
name: "End To End Emulator Tests"
|
name: "End To End Emulator Tests"
|
||||||
# Temporarily disabled. This test needs to be rewritten in playwright
|
# Temporarily disabled. This test needs to be rewritten in playwright
|
||||||
|
|||||||
@@ -124,9 +124,8 @@ export enum MongoBackendEndpointType {
|
|||||||
remote,
|
remote,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BackendApi {
|
export enum BackendApi {
|
||||||
public static readonly GenerateToken: string = "GenerateToken";
|
GenerateToken,
|
||||||
public static readonly PortalSettings: string = "PortalSettings";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PortalBackendEndpoints {
|
export class PortalBackendEndpoints {
|
||||||
@@ -179,9 +178,6 @@ export class CassandraProxyAPIs {
|
|||||||
export class Queries {
|
export class Queries {
|
||||||
public static CustomPageOption: string = "custom";
|
public static CustomPageOption: string = "custom";
|
||||||
public static UnlimitedPageOption: string = "unlimited";
|
public static UnlimitedPageOption: string = "unlimited";
|
||||||
public static setAutomaticRBACOption: string = "Automatic";
|
|
||||||
public static setTrueRBACOption: string = "True";
|
|
||||||
public static setFalseRBACOption: string = "False";
|
|
||||||
public static itemsPerPage: number = 100;
|
public static itemsPerPage: number = 100;
|
||||||
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
||||||
public static containersPerPage: number = 50;
|
public static containersPerPage: number = 50;
|
||||||
|
|||||||
@@ -690,16 +690,9 @@ export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameter
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useMongoProxyEndpoint(api: string): boolean {
|
function useMongoProxyEndpoint(api: string): boolean {
|
||||||
const activeMongoProxyEndpoints: string[] = [
|
const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac];
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
];
|
|
||||||
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||||
if (
|
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
|
||||||
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development &&
|
|
||||||
userContext.databaseAccount.properties.ipRules?.length > 0
|
|
||||||
) {
|
|
||||||
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
||||||
|
NEW_BACKEND_APIS: [BackendApi.GenerateToken],
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
NEW_MONGO_APIS: [
|
NEW_MONGO_APIS: [
|
||||||
"resourcelist",
|
"resourcelist",
|
||||||
|
|||||||
@@ -46,21 +46,9 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate() {
|
public componentDidUpdate(previous: EditorReactProps) {
|
||||||
if (!this.editor) {
|
if (this.props.content !== previous.content) {
|
||||||
return;
|
this.editor?.setValue(this.props.content);
|
||||||
}
|
|
||||||
|
|
||||||
const existingContent = this.editor.getModel().getValue();
|
|
||||||
|
|
||||||
if (this.props.content !== existingContent) {
|
|
||||||
this.editor.pushUndoStop();
|
|
||||||
this.editor.executeEdits("", [
|
|
||||||
{
|
|
||||||
range: this.editor.getModel().getFullModelRange(),
|
|
||||||
text: this.props.content,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,14 +71,9 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
|
|
||||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
|
const queryEditorModel = this.editor.getModel();
|
||||||
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
if (!this.props.isReadOnly && this.props.onContentChanged) {
|
||||||
// Hooking the model's onDidChangeContent event because of some event ordering issues.
|
queryEditorModel.onDidChangeContent(() => {
|
||||||
// If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely),
|
|
||||||
// then there are some inconsistencies as to which event fires first.
|
|
||||||
// But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event.
|
|
||||||
// (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.)
|
|
||||||
// If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first.
|
|
||||||
this.editor.onDidChangeModelContent(() => {
|
|
||||||
const queryEditorModel = this.editor.getModel();
|
const queryEditorModel = this.editor.getModel();
|
||||||
this.props.onContentChanged(queryEditorModel.getValue());
|
this.props.onContentChanged(queryEditorModel.getValue());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,13 +41,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
? Constants.Queries.UnlimitedPageOption
|
? Constants.Queries.UnlimitedPageOption
|
||||||
: Constants.Queries.CustomPageOption,
|
: Constants.Queries.CustomPageOption,
|
||||||
);
|
);
|
||||||
const [enableDataPlaneRBACOption, setEnableDataPlaneRBACOption] = useState<string>(
|
|
||||||
LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setAutomaticRBACOption
|
|
||||||
? Constants.Queries.setAutomaticRBACOption
|
|
||||||
: LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setTrueRBACOption
|
|
||||||
? Constants.Queries.setTrueRBACOption
|
|
||||||
: Constants.Queries.setFalseRBACOption
|
|
||||||
);
|
|
||||||
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
|
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
|
||||||
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
|
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
|
||||||
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
|
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
|
||||||
@@ -117,14 +110,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
StorageKey.ActualItemPerPage,
|
StorageKey.ActualItemPerPage,
|
||||||
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryString(
|
|
||||||
StorageKey.DataPlaneRbacEnabled,
|
|
||||||
enableDataPlaneRBACOption
|
|
||||||
);
|
|
||||||
|
|
||||||
LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
|
LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
|
||||||
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
|
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
|
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
|
||||||
@@ -211,12 +197,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
{ key: Constants.PriorityLevel.High, text: "High" },
|
{ key: Constants.PriorityLevel.High, text: "High" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [
|
|
||||||
{ key: Constants.Queries.setAutomaticRBACOption, text: "Automatic" },
|
|
||||||
{ key: Constants.Queries.setTrueRBACOption, text: "True" },
|
|
||||||
{ key: Constants.Queries.setFalseRBACOption, text: "False"}
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleOnPriorityLevelOptionChange = (
|
const handleOnPriorityLevelOptionChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
option: IChoiceGroupOption,
|
option: IChoiceGroupOption,
|
||||||
@@ -228,10 +208,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
setPageOption(option.key);
|
setPageOption(option.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnDataPlaneRBACOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
|
||||||
setEnableDataPlaneRBACOption(option.key);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
||||||
setRUThresholdEnabled(checked);
|
setRUThresholdEnabled(checked);
|
||||||
};
|
};
|
||||||
@@ -385,27 +361,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(
|
|
||||||
<div className="settingsSection">
|
|
||||||
<div className="settingsSectionPart">
|
|
||||||
<fieldset>
|
|
||||||
<legend id="enableDataPlaneRBACOptions" className="settingsSectionLabel legendLabel">
|
|
||||||
Enable DataPlane RBAC
|
|
||||||
</legend>
|
|
||||||
<InfoTooltip>
|
|
||||||
Choose Automatic to enable DataPlane RBAC automatically. True/False to voluntarily enable/disable DataPlane RBAC
|
|
||||||
</InfoTooltip>
|
|
||||||
<ChoiceGroup
|
|
||||||
ariaLabelledBy="enableDataPlaneRBACOptions"
|
|
||||||
selectedKey={enableDataPlaneRBACOption}
|
|
||||||
options={dataPlaneRBACOptionsList}
|
|
||||||
styles={choiceButtonStyles}
|
|
||||||
onChange={handleOnDataPlaneRBACOptionChange}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{userContext.apiType === "SQL" && (
|
{userContext.apiType === "SQL" && (
|
||||||
<>
|
<>
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FeedOptions } from "@azure/cosmos";
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
import {
|
import {
|
||||||
Areas,
|
Areas,
|
||||||
BackendApi,
|
|
||||||
ConnectionStatusType,
|
ConnectionStatusType,
|
||||||
ContainerStatusType,
|
ContainerStatusType,
|
||||||
HttpStatusCodes,
|
HttpStatusCodes,
|
||||||
@@ -31,7 +30,6 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
|
|||||||
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
||||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
|
||||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
@@ -82,11 +80,7 @@ export const isCopilotFeatureRegistered = async (subscriptionId: string): Promis
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getCopilotEnabled = async (): Promise<boolean> => {
|
export const getCopilotEnabled = async (): Promise<boolean> => {
|
||||||
const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.PortalSettings)
|
const url = `${configContext.BACKEND_ENDPOINT}/api/portalsettings/querycopilot`;
|
||||||
? configContext.PORTAL_BACKEND_ENDPOINT
|
|
||||||
: configContext.BACKEND_ENDPOINT;
|
|
||||||
|
|
||||||
const url = `${backendEndpoint}/api/portalsettings/querycopilot`;
|
|
||||||
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import * as ko from "knockout";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants";
|
|
||||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
@@ -20,6 +19,7 @@ import Explorer from "../Explorer";
|
|||||||
import * as TableConstants from "./Constants";
|
import * as TableConstants from "./Constants";
|
||||||
import * as Entities from "./Entities";
|
import * as Entities from "./Entities";
|
||||||
import * as TableEntityProcessor from "./TableEntityProcessor";
|
import * as TableEntityProcessor from "./TableEntityProcessor";
|
||||||
|
import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants";
|
||||||
|
|
||||||
export interface CassandraTableKeys {
|
export interface CassandraTableKeys {
|
||||||
partitionKeys: CassandraTableKey[];
|
partitionKeys: CassandraTableKey[];
|
||||||
@@ -732,15 +732,9 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private useCassandraProxyEndpoint(api: string): boolean {
|
private useCassandraProxyEndpoint(api: string): boolean {
|
||||||
const activeCassandraProxyEndpoints: string[] = [
|
const activeCassandraProxyEndpoints: string[] = [CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Mpac];
|
||||||
CassandraProxyEndpoints.Mpac,
|
|
||||||
CassandraProxyEndpoints.Prod,
|
|
||||||
];
|
|
||||||
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||||
if (
|
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
|
||||||
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development &&
|
|
||||||
userContext.databaseAccount.properties.ipRules?.length > 0
|
|
||||||
) {
|
|
||||||
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -496,16 +496,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onChangeContent(newContent: string): void {
|
public onChangeContent(newContent: string): void {
|
||||||
// The copilot store's active query takes precedence over the local state,
|
|
||||||
// and we can't update both states in a single operation.
|
|
||||||
// So, we update the copilot store's state first, then update the local state.
|
|
||||||
if (this.state.copilotActive) {
|
|
||||||
this.props.copilotStore?.setQuery(newContent);
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
sqlQueryEditorContent: newContent,
|
sqlQueryEditorContent: newContent,
|
||||||
queryCopilotGeneratedQuery: "",
|
queryCopilotGeneratedQuery: "",
|
||||||
});
|
});
|
||||||
|
if (this.state.copilotActive) {
|
||||||
|
this.props.copilotStore?.setQuery(newContent);
|
||||||
|
}
|
||||||
if (this.isPreferredApiMongoDB) {
|
if (this.isPreferredApiMongoDB) {
|
||||||
if (newContent.length > 0) {
|
if (newContent.length > 0) {
|
||||||
this.executeQueryButton = {
|
this.executeQueryButton = {
|
||||||
@@ -547,7 +544,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEditorContent(): string {
|
public setEditorContent(): string {
|
||||||
if (this.isCopilotTabActive && this.state.queryCopilotGeneratedQuery) {
|
if (this.isCopilotTabActive && this.state.queryCopilotGeneratedQuery) {
|
||||||
return this.state.queryCopilotGeneratedQuery;
|
return this.state.queryCopilotGeneratedQuery;
|
||||||
}
|
}
|
||||||
@@ -604,7 +601,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
<div className="queryEditor" style={{ height: "100%" }}>
|
<div className="queryEditor" style={{ height: "100%" }}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
language={"sql"}
|
language={"sql"}
|
||||||
content={this.getEditorContent()}
|
content={this.setEditorContent()}
|
||||||
isReadOnly={false}
|
isReadOnly={false}
|
||||||
wordWrap={"on"}
|
wordWrap={"on"}
|
||||||
ariaLabel={"Editing Query"}
|
ariaLabel={"Editing Query"}
|
||||||
|
|||||||
@@ -324,12 +324,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
|
|
||||||
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
||||||
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
|
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
|
||||||
if (
|
if ((userContext.apiType === "Mongo" || userContext.apiType === "Cassandra") && ipRules?.length) {
|
||||||
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development) ||
|
|
||||||
(userContext.apiType === "Cassandra" &&
|
|
||||||
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
|
|
||||||
ipRules?.length
|
|
||||||
) {
|
|
||||||
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
|
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
|
||||||
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
|
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
|
||||||
const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
|
const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// Import this first, to ensure that the dev tools hook is copied before React is loaded.
|
|
||||||
import "./ReactDevTools";
|
|
||||||
|
|
||||||
// CSS Dependencies
|
// CSS Dependencies
|
||||||
import { initializeIcons, loadTheme } from "@fluentui/react";
|
import { initializeIcons, loadTheme } from "@fluentui/react";
|
||||||
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
|
import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
import { usePortalBackendEndpoint } from "Utils/EndpointUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
||||||
import ErrorImage from "../../../../images/error.svg";
|
import ErrorImage from "../../../../images/error.svg";
|
||||||
@@ -19,7 +19,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
|
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
|
||||||
if (!useNewPortalBackendEndpoint(BackendApi.GenerateToken)) {
|
if (!usePortalBackendEndpoint(BackendApi.GenerateToken)) {
|
||||||
return await fetchEncryptedToken_ToBeDeprecated(connectionString);
|
return await fetchEncryptedToken_ToBeDeprecated(connectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export type Features = {
|
|||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
readonly executeSproc: boolean;
|
readonly executeSproc: boolean;
|
||||||
readonly enableAadDataPlane: boolean;
|
readonly enableAadDataPlane: boolean;
|
||||||
readonly enableDataPlaneRbac: boolean;
|
|
||||||
readonly enableResourceGraph: boolean;
|
readonly enableResourceGraph: boolean;
|
||||||
readonly enableKoResourceTree: boolean;
|
readonly enableKoResourceTree: boolean;
|
||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
@@ -75,7 +74,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
||||||
cosmosdb: "true" === get("cosmosdb"),
|
cosmosdb: "true" === get("cosmosdb"),
|
||||||
enableAadDataPlane: "true" === get("enableaaddataplane"),
|
enableAadDataPlane: "true" === get("enableaaddataplane"),
|
||||||
enableDataPlaneRbac: "true" === get("enabledataplanerbac"),
|
|
||||||
enableResourceGraph: "true" === get("enableresourcegraph"),
|
enableResourceGraph: "true" === get("enableresourcegraph"),
|
||||||
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
||||||
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
if (window.parent !== window) {
|
if (window.parent !== window) {
|
||||||
try {
|
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||||
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
||||||
} catch {
|
|
||||||
// No-op. We can throw here if the parent is not the same origin (such as in the Azure portal).
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ import * as StringUtility from "./StringUtility";
|
|||||||
export { LocalStorageUtility, SessionStorageUtility };
|
export { LocalStorageUtility, SessionStorageUtility };
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
ActualItemPerPage,
|
ActualItemPerPage,
|
||||||
DataPlaneRbacEnabled,
|
|
||||||
DataPlaneRbacDisabled,
|
|
||||||
isDataPlaneRbacAutomatic,
|
|
||||||
RUThresholdEnabled,
|
RUThresholdEnabled,
|
||||||
RUThreshold,
|
RUThreshold,
|
||||||
QueryTimeoutEnabled,
|
QueryTimeoutEnabled,
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ interface UserContext {
|
|||||||
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
|
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
|
||||||
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
|
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
|
||||||
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
||||||
readonly dataPlaneRbacEnabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
||||||
|
|||||||
@@ -145,22 +145,8 @@ export const allowedJunoOrigins: ReadonlyArray<string> = [
|
|||||||
|
|
||||||
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
|
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
|
||||||
|
|
||||||
//
|
export function usePortalBackendEndpoint(backendApi: BackendApi): boolean {
|
||||||
// Temporary function to determine if a portal backend API is supported by the
|
const activePortalBackendEndpoints: string[] = [PortalBackendEndpoints.Development];
|
||||||
// new backend in this environment.
|
const activeBackendApi: boolean = configContext.NEW_BACKEND_APIS?.includes(backendApi) || false;
|
||||||
//
|
return activeBackendApi && activePortalBackendEndpoints.includes(configContext.PORTAL_BACKEND_ENDPOINT as string);
|
||||||
// TODO: Remove this function once new backend migration is completed for all environments.
|
|
||||||
//
|
|
||||||
export function useNewPortalBackendEndpoint(backendApi: string): boolean {
|
|
||||||
// This maps backend APIs to the environments supported by the new backend.
|
|
||||||
const newBackendApiEnvironmentMap: { [key: string]: string[] } = {
|
|
||||||
[BackendApi.GenerateToken]: [PortalBackendEndpoints.Development],
|
|
||||||
[BackendApi.PortalSettings]: [PortalBackendEndpoints.Development, PortalBackendEndpoints.Mpac],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBackendApiEnvironmentMap[backendApi].includes(configContext.PORTAL_BACKEND_ENDPOINT);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesCon
|
|||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
@@ -271,30 +270,8 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
if (!account.properties.disableLocalAuth) {
|
||||||
var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
if (isDataPlaneRbacSetting == "Automatic")
|
|
||||||
{
|
|
||||||
if (!account.properties.disableLocalAuth) {
|
|
||||||
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
updateUserContext({
|
|
||||||
dataPlaneRbacEnabled: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(isDataPlaneRbacSetting == "True") {
|
|
||||||
updateUserContext({
|
|
||||||
dataPlaneRbacEnabled: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
|
||||||
updateUserContext({
|
|
||||||
dataPlaneRbacEnabled: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (userContext.features.enableAadDataPlane) {
|
if (userContext.features.enableAadDataPlane) {
|
||||||
@@ -416,9 +393,8 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
});
|
});
|
||||||
|
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
return new Promise(async (resolve) => {
|
return new Promise((resolve) => {
|
||||||
// In development mode, try to load the iframe message from session storage.
|
// In development mode, try to load the iframe message from session storage.
|
||||||
// This allows webpack hot reload to function properly in the portal
|
// This allows webpack hot reload to function properly in the portal
|
||||||
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
||||||
@@ -431,7 +407,6 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
console.dir(message);
|
console.dir(message);
|
||||||
updateContextsFromPortalMessage(message);
|
updateContextsFromPortalMessage(message);
|
||||||
explorer = new Explorer();
|
explorer = new Explorer();
|
||||||
|
|
||||||
// In development mode, save the iframe message from the portal in session storage.
|
// In development mode, save the iframe message from the portal in session storage.
|
||||||
// This allows webpack hot reload to funciton properly
|
// This allows webpack hot reload to funciton properly
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
@@ -440,11 +415,11 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
resolve(explorer);
|
resolve(explorer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the Portal, configuration of Explorer happens via iframe message
|
// In the Portal, configuration of Explorer happens via iframe message
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"message",
|
"message",
|
||||||
async (event) => {
|
(event) => {
|
||||||
if (isInvalidParentFrameOrigin(event)) {
|
if (isInvalidParentFrameOrigin(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -474,37 +449,6 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
setTimeout(() => explorer.openNPSSurveyDialog(), 3000);
|
setTimeout(() => explorer.openNPSSurveyDialog(), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dbAccount = userContext.databaseAccount;
|
|
||||||
let keys: DatabaseAccountListKeysResult = {};
|
|
||||||
const account = userContext.databaseAccount;
|
|
||||||
const subscriptionId = userContext.subscriptionId;
|
|
||||||
const resourceGroup = userContext.resourceGroup;
|
|
||||||
if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
|
||||||
var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
|
||||||
if (isDataPlaneRbacSetting == "Automatic")
|
|
||||||
{
|
|
||||||
if (!account.properties.disableLocalAuth) {
|
|
||||||
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
updateUserContext({
|
|
||||||
dataPlaneRbacEnabled: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(isDataPlaneRbacSetting == "True") {
|
|
||||||
updateUserContext({
|
|
||||||
dataPlaneRbacEnabled: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
|
||||||
updateUserContext({
|
|
||||||
dataPlaneRbacEnabled: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openAction) {
|
if (openAction) {
|
||||||
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
||||||
}
|
}
|
||||||
@@ -525,11 +469,9 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendReadyMessage();
|
sendReadyMessage();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user