import React from 'react'; import {Skeleton, Tab, TabList, TabProvider} from '@gravity-ui/uikit'; import {Helmet} from 'react-helmet-async'; import {Redirect, Route, Switch, useRouteMatch} from 'react-router-dom'; import {StringParam, useQueryParams} from 'use-query-params'; import {AutoRefreshControl} from '../../components/AutoRefreshControl/AutoRefreshControl'; import {DrawerContextProvider} from '../../components/Drawer/DrawerContext'; import {EntityStatus} from '../../components/EntityStatus/EntityStatus'; import {InternalLink} from '../../components/InternalLink'; import {NetworkTable} from '../../components/NetworkTable/NetworkTable'; import routes, {getClusterPath, getLocationObjectFromHref} from '../../routes'; import { useClusterDashboardAvailable, useConfigAvailable, useNodesHandlerHasWorkingClusterNetworkStats, } from '../../store/reducers/capabilities/hooks'; import { INITIAL_DEFAULT_CLUSTER_TAB, clusterApi, selectClusterTabletsWithFqdn, selectClusterTitle, updateDefaultClusterTab, useClusterBaseInfo, } from '../../store/reducers/cluster/cluster'; import {setHeaderBreadcrumbs} from '../../store/reducers/header/header'; import type {AdditionalClusterProps, AdditionalTenantsProps} from '../../types/additionalProps'; import {EFlag} from '../../types/api/enums'; import {uiFactory} from '../../uiFactory/uiFactory'; import {cn} from '../../utils/cn'; import {DEFAULT_CLUSTER_TAB_KEY} from '../../utils/constants'; import {isModifiedClickEvent} from '../../utils/events'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../utils/hooks'; import {useIsViewerUser} from '../../utils/hooks/useIsUserAllowedToMakeChanges'; import {useSetting} from '../../utils/hooks/useSetting'; import {useAppTitle} from '../App/AppTitleContext'; import {Configs} from '../Configs/Configs'; import {Nodes} from '../Nodes/Nodes'; import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {TabletsTable} from '../Tablets/TabletsTable'; import {TenantsClusterTab} from '../Tenants/TenantsClusterTab'; import {VersionsContainer} from '../Versions/Versions'; import { ClusterDrawerHealthcheck, useClusterHealthcheckQueryParams, } from './ClusterDrawerHealthcheck'; import {ClusterOverview} from './ClusterOverview/ClusterOverview'; import type {ClusterTab} from './utils'; import {clusterTabs, clusterTabsIds, isClusterTab, useShouldShowEventsTab} from './utils'; import './Cluster.scss'; const b = cn('ydb-cluster'); interface ClusterProps { additionalTenantsProps?: AdditionalTenantsProps; additionalClusterProps?: AdditionalClusterProps; } export function Cluster({additionalClusterProps, additionalTenantsProps}: ClusterProps) { const container = React.useRef(null); const isClusterDashboardAvailable = useClusterDashboardAvailable(); const shouldShowNetworkTable = useNodesHandlerHasWorkingClusterNetworkStats(); const shouldShowEventsTab = useShouldShowEventsTab(); const isViewerUser = useIsViewerUser(); const isConfigsAvailable = useConfigAvailable(); const showConfigs = isViewerUser && isConfigsAvailable; const [autoRefreshInterval] = useAutoRefreshInterval(); const dispatch = useTypedDispatch(); const {activeTabId, onClusterTabClick} = useClusterTab(); const {handleShowHealthcheckChange} = useClusterHealthcheckQueryParams(); const [{clusterName, backend}] = useQueryParams({ clusterName: StringParam, backend: StringParam, }); const viewerClusterTitle = useTypedSelector((state) => selectClusterTitle(state, clusterName ?? undefined), ); const {title: metaClusterTitle} = useClusterBaseInfo(); const clusterTitle = metaClusterTitle ?? viewerClusterTitle; const { data: {clusterData: cluster, groupsStats} = {}, isLoading: infoLoading, error, } = clusterApi.useGetClusterInfoQuery(clusterName ?? undefined, { pollingInterval: autoRefreshInterval, }); const clusterError = error && typeof error === 'object' ? error : undefined; const clusterTablets = useTypedSelector((state) => selectClusterTabletsWithFqdn(state, clusterName ?? undefined), ); React.useEffect(() => { dispatch(setHeaderBreadcrumbs('cluster', {})); }, [dispatch]); const actualClusterTabs = React.useMemo(() => { const skippedTabs: ClusterTab[] = []; if (!shouldShowNetworkTable) { skippedTabs.push(clusterTabsIds.network); } if (!shouldShowEventsTab) { skippedTabs.push(clusterTabsIds.events); } if (!showConfigs) { skippedTabs.push(clusterTabsIds.configs); } return clusterTabs.filter((el) => !skippedTabs.includes(el.id)); }, [shouldShowEventsTab, shouldShowNetworkTable, showConfigs]); const handleStatusClick = React.useCallback(() => { handleShowHealthcheckChange(true); }, [handleShowHealthcheckChange]); const getClusterTitle = () => { if (infoLoading) { return ; } const clusterStatus = cluster?.Overall || EFlag.Grey; return ( {clusterTitle} ); }; const activeTab = React.useMemo( () => actualClusterTabs.find(({id}) => id === activeTabId), [activeTabId, actualClusterTabs], ); const {appTitle} = useAppTitle(); return (
{activeTab ? {activeTab.title} : null}
{getClusterTitle()}
{isClusterDashboardAvailable && (
)}
{actualClusterTabs.map(({id, title}) => { const path = getClusterPath( {activeTab: id as ClusterTab}, { clusterName, backend, }, ); return ( {title} ); })}
{shouldShowNetworkTable && ( )} {shouldShowEventsTab && ( {uiFactory.renderEvents?.({scrollContainerRef: container})} )} {showConfigs && ( )} ( )} />
); } function useClusterTab() { const dispatch = useTypedDispatch(); const defaultTabFromStore = useTypedSelector((state) => state.cluster.defaultClusterTab); const [savedDefaultTab, setSavedDefaultTab] = useSetting( DEFAULT_CLUSTER_TAB_KEY, INITIAL_DEFAULT_CLUSTER_TAB, ); const savedDefaultTabValidated = React.useMemo(() => { return isClusterTab(savedDefaultTab) ? savedDefaultTab : undefined; }, [savedDefaultTab]); const defaultTab = savedDefaultTabValidated ?? defaultTabFromStore; const shouldShowNetworkTable = useNodesHandlerHasWorkingClusterNetworkStats(); const shouldShowEventsTab = useShouldShowEventsTab(); const match = useRouteMatch<{activeTab: string}>(routes.cluster); const {activeTab: activeTabFromParams} = match?.params || {}; let activeTab: ClusterTab; const shouldSwitchFromNetworkToDefault = !shouldShowNetworkTable && activeTabFromParams === clusterTabsIds.network; const shouldSwitchFromEventsToDefault = !shouldShowEventsTab && activeTabFromParams === clusterTabsIds.events; if (shouldSwitchFromNetworkToDefault || shouldSwitchFromEventsToDefault) { activeTab = INITIAL_DEFAULT_CLUSTER_TAB; } else if (isClusterTab(activeTabFromParams)) { activeTab = activeTabFromParams; } else { activeTab = defaultTab; } const onClusterTabClick = React.useCallback( (event: React.MouseEvent) => { if (isModifiedClickEvent(event)) { return; } const rawTabId = event.currentTarget.getAttribute('data-cluster-tab-id'); if (!rawTabId || !isClusterTab(rawTabId)) { return; } if (rawTabId !== defaultTabFromStore) { dispatch(updateDefaultClusterTab(rawTabId)); } if (rawTabId !== savedDefaultTabValidated) { setSavedDefaultTab(rawTabId); } }, [defaultTabFromStore, dispatch, savedDefaultTabValidated, setSavedDefaultTab], ); return {activeTabId: activeTab, onClusterTabClick} as const; }