cloudshell screenshot fix

This commit is contained in:
nishthaAhujaa 2025-08-18 18:44:24 +05:30
parent 012d043c78
commit 811a6dd363
4 changed files with 207 additions and 25 deletions

View File

@ -0,0 +1,71 @@
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 "";
}

View File

@ -0,0 +1,78 @@
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,
};
}

View File

@ -22,10 +22,22 @@ export abstract class BaseTerminalComponentAdapter implements ReactAdapter {
protected getUsername: () => string,
protected isAllPublicIPAddressesEnabled: ko.Observable<boolean>,
protected kind: ViewModels.TerminalKind,
) {}
protected isCloudShellIPsConfigured?: ko.Observable<boolean>,
) { }
public renderComponent(): JSX.Element {
if (!this.isAllPublicIPAddressesEnabled()) {
const publicIPEnabled = this.isAllPublicIPAddressesEnabled();
const cloudShellConfigured = this.isCloudShellIPsConfigured ? this.isCloudShellIPsConfigured() : true;
let shouldShowScreenshot: boolean;
if (this.isCloudShellIPsConfigured) {
shouldShowScreenshot = !cloudShellConfigured;
} else {
shouldShowScreenshot = !publicIPEnabled;
}
if (shouldShowScreenshot) {
return (
<QuickstartFirewallNotification
messageType={this.getMessageType()}

View File

@ -1,4 +1,5 @@
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
import { checkCloudShellIPsConfigured } from "Explorer/Tabs/Shared/CloudShellIPChecker";
import { CloudShellTerminalComponentAdapter } from "Explorer/Tabs/ShellAdapters/CloudShellTerminalComponentAdapter";
import * as ko from "knockout";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
@ -23,11 +24,13 @@ export default class TerminalTab extends TabsBase {
private container: Explorer;
private notebookTerminalComponentAdapter: ReactAdapter;
private isAllPublicIPAddressesEnabled: ko.Observable<boolean>;
private isCloudShellIPsConfigured: ko.Observable<boolean>;
constructor(options: TerminalTabOptions) {
super(options);
this.container = options.container;
this.isAllPublicIPAddressesEnabled = ko.observable(true);
this.isCloudShellIPsConfigured = ko.observable(true); // Start optimistic, will be updated
const commonArgs: [
() => DataModels.DatabaseAccount,
@ -44,10 +47,25 @@ export default class TerminalTab extends TabsBase {
];
if (userContext.features.enableCloudShell) {
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(...commonArgs);
this.notebookTerminalComponentAdapter = new CloudShellTerminalComponentAdapter(
() => userContext?.databaseAccount,
() => this.tabId,
() => this.getUsername(),
this.isAllPublicIPAddressesEnabled,
options.kind,
this.isCloudShellIPsConfigured,
);
this.notebookTerminalComponentAdapter.parameters = ko.computed<boolean>(() => {
return this.isTemplateReady() && this.isAllPublicIPAddressesEnabled();
const cloudShellConfigured = this.isCloudShellIPsConfigured();
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 {
this.notebookTerminalComponentAdapter = new NotebookTerminalComponentAdapter(
@ -65,6 +83,8 @@ export default class TerminalTab extends TabsBase {
});
}
// Only run legacy firewall checks for NON-CloudShell terminals cloudShell terminals use the CloudShell IP checker instead
if (!userContext.features.enableCloudShell) {
if (options.kind === ViewModels.TerminalKind.Postgres) {
checkFirewallRules(
"2022-11-08",
@ -83,6 +103,7 @@ export default class TerminalTab extends TabsBase {
);
}
}
}
public getContainer(): Explorer {
return this.container;