diff --git a/images/firewallRule.png b/images/firewallRule.png
new file mode 100644
index 000000000..fd388bd4d
Binary files /dev/null and b/images/firewallRule.png differ
diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts
index d128256c1..031a0686b 100644
--- a/src/Contracts/DataModels.ts
+++ b/src/Contracts/DataModels.ts
@@ -567,6 +567,16 @@ export interface ContainerConnectionInfo {
//need to add ram and rom info
}
+export interface PostgresFirewallRule {
+ id: string;
+ name: string;
+ type: string;
+ properties: {
+ startIpAddress: string;
+ endIpAddress: string;
+ };
+}
+
export enum PhoenixErrorType {
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
diff --git a/src/Explorer/Quickstart/QuickstartFirewallNotification.tsx b/src/Explorer/Quickstart/QuickstartFirewallNotification.tsx
new file mode 100644
index 000000000..adba30082
--- /dev/null
+++ b/src/Explorer/Quickstart/QuickstartFirewallNotification.tsx
@@ -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 => (
+
+
+ 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).
+
+ We strongly recommend removing this rule once you finish using the PostgreSQL shell.
+
+ sendMessage({ type: MessageTypes.OpenPostgresNetworkingBlade })}
+ >
+ Add firewall rule
+
+
+);
diff --git a/src/Explorer/Quickstart/QuickstartGuide.tsx b/src/Explorer/Quickstart/QuickstartGuide.tsx
index 46ed22d8b..9df39de0f 100644
--- a/src/Explorer/Quickstart/QuickstartGuide.tsx
+++ b/src/Explorer/Quickstart/QuickstartGuide.tsx
@@ -109,8 +109,12 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
To begin, please enter the cluster's password in the PostgreSQL terminal.
+
+
+ Note: If you navigate out of the Quick Start tab (PostgreSQL Shell), the session will be closed and
+ all ongoing commands might be interrupted.
-
+
{
onClick={() => setCurrentStep(1)}
>
- Let’s create two tables github_users and github_events in “cosmosdb_tutorial” schema.
+ Let's create two tables github_users and github_events in “cosmosdb_tutorial” schema.
useTerminal.getState().sendMessage(newTableCommand)}
@@ -150,7 +154,7 @@ export const QuickstartGuide: React.FC = (): JSX.Element => {
onClick={() => onCopyBtnClicked("#newTableCommand")}
/>
-
+
{
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
/>
-
+
{
onClick={() => onCopyBtnClicked("#loadDataCommand")}
/>
-
+
{
onClick={() => onCopyBtnClicked("#queryCommand")}
/>
-
+
diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx
index d6ac666d9..10882f8e8 100644
--- a/src/Explorer/SplashScreen/SplashScreen.tsx
+++ b/src/Explorer/SplashScreen/SplashScreen.tsx
@@ -347,10 +347,10 @@ export class SplashScreen extends React.Component {
const connectBtn = {
iconSrc: ConnectIcon,
- title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
+ title: userContext.apiType === "Postgres" ? "Connect with pgAdmin" : "Connect",
description:
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",
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
};
diff --git a/src/Explorer/Tabs/PostgresConnectTab.tsx b/src/Explorer/Tabs/PostgresConnectTab.tsx
index 3487f7ee3..732b1d93f 100644
--- a/src/Explorer/Tabs/PostgresConnectTab.tsx
+++ b/src/Explorer/Tabs/PostgresConnectTab.tsx
@@ -120,7 +120,7 @@ You can enable or disable public IP addresses on the worker nodes on 'Networking
-
+
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
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
+
+
+
+ Refer to our{" "}
+
+ guide
+ {" "}
+ to help you connect via pgAdmin.
+
);
};
diff --git a/src/Explorer/Tabs/QuickstartTab.tsx b/src/Explorer/Tabs/QuickstartTab.tsx
index 12c94b8e3..5a3aac58c 100644
--- a/src/Explorer/Tabs/QuickstartTab.tsx
+++ b/src/Explorer/Tabs/QuickstartTab.tsx
@@ -1,11 +1,15 @@
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 Explorer from "Explorer/Explorer";
import { useNotebook } from "Explorer/Notebook/useNotebook";
+import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
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 { armRequest } from "Utils/arm/request";
interface QuickstartTabProps {
explorer: Explorer;
@@ -13,29 +17,58 @@ interface QuickstartTabProps {
export const QuickstartTab: React.FC = ({ explorer }: QuickstartTabProps): JSX.Element => {
const notebookServerInfo = useNotebook((state) => state.notebookServerInfo);
- useEffect(() => {
- explorer.allocateContainer();
- }, []);
+ const [isAllPublicIPAddressEnabled, setIsAllPublicIPAddressEnabled] = useState(true);
+
const getNotebookServerInfo = (): NotebookWorkspaceConnectionInfo => ({
authToken: notebookServerInfo.authToken,
notebookServerEndpoint: `${notebookServerInfo.notebookServerEndpoint?.replace(/\/+$/, "")}/postgresql`,
forwardingId: notebookServerInfo.forwardingId,
});
+ const checkFirewallRules = async (): Promise => {
+ 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 (
- {notebookServerInfo?.notebookServerEndpoint && (
+ {!isAllPublicIPAddressEnabled && }
+ {isAllPublicIPAddressEnabled && notebookServerInfo?.notebookServerEndpoint && (
)}
- {!notebookServerInfo?.notebookServerEndpoint && (
+ {isAllPublicIPAddressEnabled && !notebookServerInfo?.notebookServerEndpoint && (
)}
diff --git a/src/Explorer/Tabs/TerminalTab.tsx b/src/Explorer/Tabs/TerminalTab.tsx
index 2b2761d06..708352073 100644
--- a/src/Explorer/Tabs/TerminalTab.tsx
+++ b/src/Explorer/Tabs/TerminalTab.tsx
@@ -1,6 +1,9 @@
import { Spinner, SpinnerSize } from "@fluentui/react";
+import { configContext } from "ConfigContext";
+import { QuickstartFirewallNotification } from "Explorer/Quickstart/QuickstartFirewallNotification";
import * as ko from "knockout";
import * as React from "react";
+import { armRequest } from "Utils/arm/request";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
@@ -26,10 +29,15 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
constructor(
private getNotebookServerInfo: () => DataModels.NotebookWorkspaceConnectionInfo,
private getDatabaseAccount: () => DataModels.DatabaseAccount,
- private getTabId: () => string
+ private getTabId: () => string,
+ private isAllPublicIPAddressesEnabled: ko.Observable
) {}
public renderComponent(): JSX.Element {
+ if (!this.isAllPublicIPAddressesEnabled()) {
+ return ;
+ }
+
return this.parameters() ? (
;
constructor(options: TerminalTabOptions) {
super(options);
this.container = options.container;
+ this.isAllPublicIPAddressesEnabled = ko.observable(true);
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
() => this.getNotebookServerInfo(options),
() => userContext?.databaseAccount,
- () => this.tabId
+ () => this.tabId,
+ this.isAllPublicIPAddressesEnabled
);
this.notebookTerminalComponentAdapter.parameters = ko.computed(() => {
if (
this.isTemplateReady() &&
useNotebook.getState().isNotebookEnabled &&
- useNotebook.getState().notebookServerInfo?.notebookServerEndpoint
+ useNotebook.getState().notebookServerInfo?.notebookServerEndpoint &&
+ this.isAllPublicIPAddressesEnabled()
) {
return true;
}
return false;
});
+
+ if (options.kind === ViewModels.TerminalKind.Postgres) {
+ this.checkPostgresFirewallRules();
+ }
}
public getContainer(): Explorer {
@@ -110,4 +126,25 @@ export default class TerminalTab extends TabsBase {
forwardingId: info.forwardingId,
};
}
+
+ private async checkPostgresFirewallRules(): Promise {
+ 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);
+ }
+ }
}