Add firewall notification in quickstart tab (#1337)

This commit is contained in:
victor-meng 2022-10-10 19:30:52 -07:00 committed by GitHub
parent 5b365e642f
commit 53b5ebd39c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 136 additions and 19 deletions

BIN
images/firewallRule.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -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",

View File

@ -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>
);

View File

@ -109,8 +109,12 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
<br /> <br />
<br /> <br />
To begin, please enter the cluster&apos;s password in the PostgreSQL terminal. To begin, please enter the cluster&apos;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>Lets create two tables github_users and github_events in cosmosdb_tutorial schema.</Text> <Text>Let&apos;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>

View File

@ -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),
}; };

View File

@ -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 &apos;verify-full&apos; Only secure connections are supported. For production use cases, we recommend using the &apos;verify-full&apos;
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>
); );
}; };

View File

@ -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>

View File

@ -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);
}
}
} }