Add firewall notification in quickstart tab (#1337)
This commit is contained in:
parent
5b365e642f
commit
53b5ebd39c
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -567,6 +567,16 @@ export interface ContainerConnectionInfo {
|
||||||
//need to add ram and rom info
|
//need to add ram and rom info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PostgresFirewallRule {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
startIpAddress: string;
|
||||||
|
endIpAddress: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export enum PhoenixErrorType {
|
export enum PhoenixErrorType {
|
||||||
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
|
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
|
||||||
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
|
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Image, PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||||
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const QuickstartFirewallNotification: React.FC = (): JSX.Element => (
|
||||||
|
<Stack style={{ padding: "16px 20px" }}>
|
||||||
|
<Text block>
|
||||||
|
To use the PostgreSQL shell, you need to add a firewall rule to allow access from all IP addresses
|
||||||
|
(0.0.0.0-255.255.255).
|
||||||
|
</Text>
|
||||||
|
<Text block>We strongly recommend removing this rule once you finish using the PostgreSQL shell.</Text>
|
||||||
|
<Image style={{ margin: "20px 0" }} src="../../../images/firewallRule.png" />
|
||||||
|
<PrimaryButton
|
||||||
|
style={{ width: 150 }}
|
||||||
|
onClick={() => sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}
|
||||||
|
>
|
||||||
|
Add firewall rule
|
||||||
|
</PrimaryButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
|
@ -109,8 +109,12 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
To begin, please enter the cluster's password in the PostgreSQL terminal.
|
To begin, please enter the cluster's password in the PostgreSQL terminal.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Note: If you navigate out of the Quick Start tab (PostgreSQL Shell), the session will be closed and
|
||||||
|
all ongoing commands might be interrupted.
|
||||||
</Text>
|
</Text>
|
||||||
<Youtube videoId="UaBDXHMQAUw" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="nT64dFSfiUo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
|
@ -120,7 +124,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||||
onClick={() => setCurrentStep(1)}
|
onClick={() => setCurrentStep(1)}
|
||||||
>
|
>
|
||||||
<Stack style={{ marginTop: 20 }}>
|
<Stack style={{ marginTop: 20 }}>
|
||||||
<Text>Let’s create two tables github_users and github_events in “cosmosdb_tutorial” schema.</Text>
|
<Text>Let's create two tables github_users and github_events in “cosmosdb_tutorial” schema.</Text>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
style={{ marginTop: 16, width: 150 }}
|
style={{ marginTop: 16, width: 150 }}
|
||||||
onClick={() => useTerminal.getState().sendMessage(newTableCommand)}
|
onClick={() => useTerminal.getState().sendMessage(newTableCommand)}
|
||||||
|
@ -150,7 +154,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||||
onClick={() => onCopyBtnClicked("#newTableCommand")}
|
onClick={() => onCopyBtnClicked("#newTableCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="VJqupvSQ-mw" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="il_sA6U1WcY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
|
@ -195,7 +199,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||||
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
|
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="Q-AW7q1GLDY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="kCCDRRrN1r0" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
|
@ -235,7 +239,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||||
onClick={() => onCopyBtnClicked("#loadDataCommand")}
|
onClick={() => onCopyBtnClicked("#loadDataCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="h15fvLKXzRo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="XSMEE2tujEk" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem
|
<PivotItem
|
||||||
|
@ -277,7 +281,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
|
||||||
onClick={() => onCopyBtnClicked("#queryCommand")}
|
onClick={() => onCopyBtnClicked("#queryCommand")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Youtube videoId="p46nRnE4b8Y" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
<Youtube videoId="k_EanjMtaPg" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
</Pivot>
|
</Pivot>
|
||||||
|
|
|
@ -347,10 +347,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||||
|
|
||||||
const connectBtn = {
|
const connectBtn = {
|
||||||
iconSrc: ConnectIcon,
|
iconSrc: ConnectIcon,
|
||||||
title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
|
title: userContext.apiType === "Postgres" ? "Connect with pgAdmin" : "Connect",
|
||||||
description:
|
description:
|
||||||
userContext.apiType === "Postgres"
|
userContext.apiType === "Postgres"
|
||||||
? "Prefer PGadmin? Find your connection strings here"
|
? "Prefer pgAdmin? Find your connection strings here"
|
||||||
: "Prefer using your own choice of tooling? Find the connection string you need to connect",
|
: "Prefer using your own choice of tooling? Find the connection string you need to connect",
|
||||||
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
|
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
|
||||||
};
|
};
|
||||||
|
|
|
@ -120,7 +120,7 @@ You can enable or disable public IP addresses on the worker nodes on 'Networking
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Label>Secure connections</Label>
|
<Label>Secure connections</Label>
|
||||||
<Text>
|
<Text style={{ marginBottom: 8 }}>
|
||||||
Only secure connections are supported. For production use cases, we recommend using the 'verify-full'
|
Only secure connections are supported. For production use cases, we recommend using the 'verify-full'
|
||||||
mode to enforce TLS certificate verification. You will need to download the Hyperscale (Citus) certificate, and
|
mode to enforce TLS certificate verification. You will need to download the Hyperscale (Citus) certificate, and
|
||||||
provide it when connecting to the database.{" "}
|
provide it when connecting to the database.{" "}
|
||||||
|
@ -128,6 +128,18 @@ You can enable or disable public IP addresses on the worker nodes on 'Networking
|
||||||
Learn more
|
Learn more
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<Label>Connect with pgAdmin</Label>
|
||||||
|
<Text>
|
||||||
|
Refer to our{" "}
|
||||||
|
<Link
|
||||||
|
href="https://learn.microsoft.com/en-us/azure/postgresql/hyperscale/howto-connect?tabs=pgadmin"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
guide
|
||||||
|
</Link>{" "}
|
||||||
|
to help you connect via pgAdmin.
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
||||||
import { NotebookWorkspaceConnectionInfo } from "Contracts/DataModels";
|
import { configContext } from "ConfigContext";
|
||||||
|
import { NotebookWorkspaceConnectionInfo, PostgresFirewallRule } from "Contracts/DataModels";
|
||||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
|
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||||
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
import { QuickstartGuide } from "Explorer/Quickstart/QuickstartGuide";
|
||||||
import React, { useEffect } from "react";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
|
import { armRequest } from "Utils/arm/request";
|
||||||
|
|
||||||
interface QuickstartTabProps {
|
interface QuickstartTabProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
|
@ -13,29 +17,58 @@ interface QuickstartTabProps {
|
||||||
|
|
||||||
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
|
export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: QuickstartTabProps): JSX.Element => {
|
||||||
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
|
||||||
useEffect(() => {
|
const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState<boolean>(true);
|
||||||
explorer.allocateContainer();
|
|
||||||
}, []);
|
|
||||||
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
|
||||||
authToken: notebookServerInfo.authToken,
|
authToken: notebookServerInfo.authToken,
|
||||||
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
|
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
|
||||||
forwardingId: notebookServerInfo.forwardingId,
|
forwardingId: notebookServerInfo.forwardingId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const checkFirewallRules = async (): Promise<void> => {
|
||||||
|
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const response: any = await armRequest({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: firewallRulesUri,
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: "2020-10-05-privatepreview",
|
||||||
|
});
|
||||||
|
const firewallRules: PostgresFirewallRule[] = response?.data?.value || response?.value || [];
|
||||||
|
const isEnabled = firewallRules.some(
|
||||||
|
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
|
||||||
|
);
|
||||||
|
setIsAllPublicIPAddressEnabled(isEnabled);
|
||||||
|
|
||||||
|
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
|
||||||
|
if (!isEnabled && useTabs.getState().activeReactTab === ReactTabKind.Quickstart) {
|
||||||
|
setTimeout(checkFirewallRules, 30000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkFirewallRules();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
explorer.allocateContainer();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack style={{ width: "100%" }} horizontal>
|
<Stack style={{ width: "100%" }} horizontal>
|
||||||
<Stack style={{ width: "50%" }}>
|
<Stack style={{ width: "50%" }}>
|
||||||
<QuickstartGuide />
|
<QuickstartGuide />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
<Stack style={{ width: "50%", borderLeft: "black solid 1px" }}>
|
||||||
{notebookServerInfo?.notebookServerEndpoint && (
|
{!isAllPublicIPAddressEnabled && <QuickstartFirewallNotification />}
|
||||||
|
{isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
|
||||||
<NotebookTerminalComponent
|
<NotebookTerminalComponent
|
||||||
notebookServerInfo={getNotebookServerInfo()}
|
notebookServerInfo={getNotebookServerInfo()}
|
||||||
databaseAccount={userContext.databaseAccount}
|
databaseAccount={userContext.databaseAccount}
|
||||||
tabId="QuickstartPSQLShell"
|
tabId="QuickstartPSQLShell"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!notebookServerInfo?.notebookServerEndpoint && (
|
{isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
|
||||||
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
<Spinner styles={{ root: { marginTop: 10 } }} size={SpinnerSize.large}></Spinner>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { Spinner, SpinnerSize } from "@fluentui/react";
|
import { Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
|
import { configContext } from "ConfigContext";
|
||||||
|
import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { armRequest } from "Utils/arm/request";
|
||||||
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";
|
||||||
|
@ -26,10 +29,15 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
||||||
constructor(
|
constructor(
|
||||||
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
|
||||||
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
private getDatabaseAccount: () => DataModels.DatabaseAccount,
|
||||||
private getTabId: () => string
|
private getTabId: () => string,
|
||||||
|
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
|
if (!this.isAllPublicIPAddressesEnabled()) {
|
||||||
|
return <QuickstartFirewallNotification />;
|
||||||
|
}
|
||||||
|
|
||||||
return this.parameters() ? (
|
return this.parameters() ? (
|
||||||
<NotebookTerminalComponent
|
<NotebookTerminalComponent
|
||||||
notebookServerInfo={this.getNotebookServerInfo()}
|
notebookServerInfo={this.getNotebookServerInfo()}
|
||||||
|
@ -46,25 +54,33 @@ export default class TerminalTab extends TabsBase {
|
||||||
public readonly html = '<div style="height: 100%" data-bind="react:notebookTerminalComponentAdapter"></div> ';
|
public readonly html = '<div style="height: 100%" data-bind="react:notebookTerminalComponentAdapter"></div> ';
|
||||||
private container: Explorer;
|
private container: Explorer;
|
||||||
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;
|
private notebookTerminalComponentAdapter: NotebookTerminalComponentAdapter;
|
||||||
|
private isAllPublicIPAddressesEnabled: 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.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
|
||||||
() => this.getNotebookServerInfo(options),
|
() => this.getNotebookServerInfo(options),
|
||||||
() => userContext?.databaseAccount,
|
() => userContext?.databaseAccount,
|
||||||
() => this.tabId
|
() => this.tabId,
|
||||||
|
this.isAllPublicIPAddressesEnabled
|
||||||
);
|
);
|
||||||
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
if (
|
if (
|
||||||
this.isTemplateReady() &&
|
this.isTemplateReady() &&
|
||||||
useNotebook.getState().isNotebookEnabled &&
|
useNotebook.getState().isNotebookEnabled &&
|
||||||
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
|
useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
|
||||||
|
this.isAllPublicIPAddressesEnabled()
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (options.kind === ViewModels.TerminalKind.Postgres) {
|
||||||
|
this.checkPostgresFirewallRules();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
|
@ -110,4 +126,25 @@ export default class TerminalTab extends TabsBase {
|
||||||
forwardingId: info.forwardingId,
|
forwardingId: info.forwardingId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async checkPostgresFirewallRules(): Promise<void> {
|
||||||
|
const firewallRulesUri = `${userContext.databaseAccount.id}/firewallRules`;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const response: any = await armRequest({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: firewallRulesUri,
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: "2020-10-05-privatepreview",
|
||||||
|
});
|
||||||
|
const firewallRules: DataModels.PostgresFirewallRule[] = response?.data?.value || response?.value || [];
|
||||||
|
const isEnabled = firewallRules.some(
|
||||||
|
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"
|
||||||
|
);
|
||||||
|
this.isAllPublicIPAddressesEnabled(isEnabled);
|
||||||
|
|
||||||
|
// If the firewall rule is not added, check every 30 seconds to see if the user has added the rule
|
||||||
|
if (!isEnabled) {
|
||||||
|
setTimeout(() => this.checkPostgresFirewallRules(), 30000);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue