import { IMessageBarStyles, MessageBar, MessageBarType } from "@fluentui/react"; import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants"; import { configContext } from "ConfigContext"; import { IpRule } from "Contracts/DataModels"; import { CollectionTabKind } from "Contracts/ViewModels"; import Explorer from "Explorer/Explorer"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab"; import { SplashScreen } from "Explorer/SplashScreen/SplashScreen"; import { ConnectTab } from "Explorer/Tabs/ConnectTab"; import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab"; import { QuickstartTab } from "Explorer/Tabs/QuickstartTab"; import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab"; import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab"; import { LayoutConstants } from "Explorer/Theme/ThemeUtil"; import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { userContext } from "UserContext"; import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendIPs } from "Utils/EndpointUtils"; import { useTeachingBubble } from "hooks/useTeachingBubble"; import ko from "knockout"; import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; import errorIcon from "../../../images/close-black.svg"; import errorQuery from "../../../images/error_no_outline.svg"; import { useObservable } from "../../hooks/useObservable"; import { ReactTabKind, useTabs } from "../../hooks/useTabs"; import TabsBase from "./TabsBase"; type Tab = TabsBase | (TabsBase & { render: () => JSX.Element }); interface TabsProps { explorer: Explorer; } export const Tabs = ({ explorer }: TabsProps): JSX.Element => { const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs(); const [ showMongoAndCassandraProxiesNetworkSettingsWarningState, setShowMongoAndCassandraProxiesNetworkSettingsWarningState, ] = useState(showMongoAndCassandraProxiesNetworkSettingsWarning()); const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.TABS); useEffect(() => { setKeyboardHandlers({ [KeyboardAction.SELECT_LEFT_TAB]: () => useTabs.getState().selectLeftTab(), [KeyboardAction.SELECT_RIGHT_TAB]: () => useTabs.getState().selectRightTab(), [KeyboardAction.CLOSE_TAB]: () => useTabs.getState().closeActiveTab(), }); }, [setKeyboardHandlers]); const defaultMessageBarStyles: IMessageBarStyles = { root: { height: `${LayoutConstants.rowHeight}px`, overflow: "hidden", flexDirection: "row", }, }; return (
{showMongoAndCassandraProxiesNetworkSettingsWarningState && ( { setShowMongoAndCassandraProxiesNetworkSettingsWarningState(false); }} > {`We have migrated our middleware to new infrastructure. To avoid issues with Data Explorer access, please re-enable "Allow access from Azure Portal" on the Networking blade for your account.`} )}
{activeReactTab !== undefined && getReactTabContent(activeReactTab, explorer)} {openedTabs.map((tab) => ( ))}
); }; function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?: ReactTabKind }) { const [hovering, setHovering] = useState(false); const focusTab = useRef() as MutableRefObject; const tabId = tab ? tab.tabId : ""; const getReactTabTitle = (): ko.Observable => { if (tabKind === ReactTabKind.QueryCopilot) { return ko.observable("Query"); } return ko.observable(ReactTabKind[tabKind]); }; const tabTitle = useObservable(tab?.tabTitle || getReactTabTitle()); useEffect(() => { if (active && focusTab.current) { focusTab.current.focus(); } }, [active]); return (
  • setHovering(true)} onMouseLeave={() => setHovering(false)} className={active ? "active tabList" : "tabList"} style={active ? { fontWeight: "bolder" } : {}} role="presentation" >
    { if (tab) { tab.onTabClick(); } else if (tabKind !== undefined) { useTabs.getState().activateReactTab(tabKind); } }} onKeyPress={({ nativeEvent: e }) => { if (tab) { tab.onKeyPressActivate(undefined, e); } else if (tabKind !== undefined) { onKeyPressReactTab(e, tabKind); } }} title={useObservable(tab?.tabPath || ko.observable(""))} aria-selected={active} aria-expanded={active} aria-controls={tabId} tabIndex={0} role="tab" ref={focusTab} > {useObservable(tab?.isExecutionError || ko.observable(false)) && } {isTabExecuting(tab, tabKind) && ( Loading )} {isQueryErrorThrown(tab, tabKind) && ( Error )} {tabTitle}
  • ); } const onKeyPressReactTabClose = (e: KeyboardEvent, tabKind: ReactTabKind): void => { if (e.key === "Enter" || e.code === "Space") { useTabs.getState().closeReactTab(tabKind); e.stopPropagation(); } }; const CloseButton = ({ tab, active, hovering, tabKind, ariaLabel, }: { tab: Tab; active: boolean; hovering: boolean; tabKind?: ReactTabKind; ariaLabel: string; }) => ( ) => { event.stopPropagation(); tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeReactTab(tabKind); // tabKind === ReactTabKind.QueryCopilot && useQueryCopilot.getState().resetQueryCopilotStates(); }} tabIndex={active ? 0 : undefined} onKeyPress={({ nativeEvent: e }) => (tab ? tab.onKeyPressClose(undefined, e) : onKeyPressReactTabClose(e, tabKind))} > Close ); const ErrorIcon = ({ tab, active }: { tab: Tab; active: boolean }) => (
    tab.onErrorDetailsClick(undefined, e)} onKeyPress={({ nativeEvent: e }) => tab.onErrorDetailsKeyPress(undefined, e)} >
    ); function TabPane({ tab, active }: { tab: Tab; active: boolean }) { const ref = useRef(); const attrs = { style: { display: active ? undefined : "none" }, className: "tabs-container", }; useEffect((): (() => void) | void => { if (tab.tabKind === CollectionTabKind.Documents && tab.collection?.isSampleCollection) { useTeachingBubble.getState().setIsDocumentsTabOpened(true); } const { current: element } = ref; if (element) { ko.applyBindings(tab, element); const ctx = ko.contextFor(element).createChildContext(tab); ko.applyBindingsToDescendants(ctx, element); tab.isTemplateReady(true); return () => ko.cleanNode(element); } }, [ref, tab]); if (tab) { if ("render" in tab) { return (
    {tab.render()}
    ); } } return
    ; } const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => { if (e.key === "Enter" || e.code === "Space") { useTabs.getState().activateReactTab(tabKind); e.stopPropagation(); } }; const isTabExecuting = (tab?: Tab, tabKind?: ReactTabKind): boolean => { if (useObservable(tab?.isExecuting || ko.observable(false))) { return true; } else if (tabKind !== undefined && tabKind !== ReactTabKind.Home && useTabs.getState()?.isTabExecuting) { return true; } return false; }; const isQueryErrorThrown = (tab?: Tab, tabKind?: ReactTabKind): boolean => { if ( !tab?.isExecuting && tabKind !== undefined && tabKind !== ReactTabKind.Home && useTabs.getState()?.isQueryErrorThrown && !useTabs.getState()?.isTabExecuting ) { return true; } return false; }; const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => { // React tabs have no context buttons. useCommandBar.getState().setContextButtons([]); // eslint-disable-next-line no-console switch (activeReactTab) { case ReactTabKind.Connect: return userContext.apiType === "VCoreMongo" ? ( ) : userContext.apiType === "Postgres" ? ( ) : ( ); case ReactTabKind.Home: return ; case ReactTabKind.Quickstart: return userContext.apiType === "VCoreMongo" ? ( ) : ( ); case ReactTabKind.QueryCopilot: return ; default: throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`); } }; const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules; if ( ((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 ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange); const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) => ipAddressesFromIPRules.includes(legacyPortalBackendIP), ); if (!ipRulesIncludeLegacyPortalBackend) { return false; } if (userContext.apiType === "Mongo") { const isProdOrMpacMongoProxyEndpoint: boolean = [MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Prod].includes( configContext.MONGO_PROXY_ENDPOINT, ); const mongoProxyOutboundIPs: string[] = isProdOrMpacMongoProxyEndpoint ? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]] : MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT]; const ipRulesIncludeMongoProxy: boolean = mongoProxyOutboundIPs.every((mongoProxyOutboundIP: string) => ipAddressesFromIPRules.includes(mongoProxyOutboundIP), ); return !ipRulesIncludeMongoProxy; } else if (userContext.apiType === "Cassandra") { const isProdOrMpacCassandraProxyEndpoint: boolean = [ CassandraProxyEndpoints.Mpac, CassandraProxyEndpoints.Prod, ].includes(configContext.CASSANDRA_PROXY_ENDPOINT); const cassandraProxyOutboundIPs: string[] = isProdOrMpacCassandraProxyEndpoint ? [ ...CassandraProxyOutboundIPs[CassandraProxyEndpoints.Mpac], ...CassandraProxyOutboundIPs[CassandraProxyEndpoints.Prod], ] : CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT]; const ipRulesIncludeCassandraProxy: boolean = cassandraProxyOutboundIPs.every( (cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP), ); return !ipRulesIncludeCassandraProxy; } } return false; };