import React from 'react'; import { Archive, ArrowChevronRight, Ban, ChevronRight, CirclePause, LinkSlash, MapPin, } from '@gravity-ui/icons'; import {Flex, Icon, Label, Text} from '@gravity-ui/uikit'; import type {LabelProps} from '@gravity-ui/uikit'; import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus'; import {LabelWithHelpMark} from '../../../../components/LabelWithHelpMark/LabelWithHelpMark'; import {Skeleton} from '../../../../components/Skeleton/Skeleton'; import { healthcheckApi, selectLeavesIssues, } from '../../../../store/reducers/healthcheckInfo/healthcheckInfo'; import type {TBridgePile} from '../../../../types/api/cluster'; import {BridgePileState} from '../../../../types/api/cluster'; import {cn} from '../../../../utils/cn'; import {EMPTY_DATA_PLACEHOLDER} from '../../../../utils/constants'; import {formatNumber} from '../../../../utils/dataFormatters/dataFormatters'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; import {useClusterHealthcheckQueryParams} from '../../ClusterDrawerHealthcheck'; import i18n from '../../i18n'; import type {BridgePileHealthcheckTarget} from './bridgePileHealthcheck'; import {getBridgePileHealthcheck} from './bridgePileHealthcheck'; import './BridgeInfoTable.scss'; const b = cn('ydb-bridge-info-table'); interface BridgePileStateInfo { label?: string; theme: NonNullable; icon?: React.ReactNode; help?: string; } function getBridgePileStateInfo(state?: BridgePileState): BridgePileStateInfo { switch (state) { case BridgePileState.PRIMARY: return { label: i18n('value_bridge-state-primary'), theme: 'normal', help: i18n('context_bridge-pile-state-primary'), }; case BridgePileState.PROMOTED: return { label: i18n('value_bridge-state-promoted'), theme: 'info', icon: , help: i18n('context_bridge-pile-state-promoted'), }; case BridgePileState.SYNCHRONIZED: return { label: i18n('value_bridge-state-synchronised'), theme: 'normal', help: i18n('context_bridge-pile-state-synchronised'), }; case BridgePileState.NOT_SYNCHRONIZED: return { label: i18n('value_bridge-state-not-synchronised'), theme: 'warning', icon: , help: i18n('context_bridge-pile-state-not-synchronised'), }; case BridgePileState.SUSPENDED: return { label: i18n('value_bridge-state-suspended'), theme: 'warning', icon: , help: i18n('context_bridge-pile-state-suspended'), }; case BridgePileState.DISCONNECTED: return { label: i18n('value_bridge-state-disconnected'), theme: 'danger', icon: , help: i18n('context_bridge-pile-state-disconnected'), }; case BridgePileState.UNSPECIFIED: return { label: i18n('value_bridge-state-unspecified'), theme: 'unknown', }; default: return { label: state, theme: 'unknown', }; } } interface BridgeInfoTableProps { piles: TBridgePile[]; database?: string; clusterName?: string; } interface BridgePileCardProps { pile: TBridgePile; current: boolean; healthcheckAvailable: boolean; healthcheckSupportsPiles: boolean; healthcheckLoading: boolean; leavesIssues: ReturnType; onHealthcheckClick: (target: BridgePileHealthcheckTarget) => void; } const BridgePileCard = React.memo(function BridgePileCard({ pile, current, healthcheckAvailable, healthcheckSupportsPiles, healthcheckLoading, leavesIssues, onHealthcheckClick, }: BridgePileCardProps) { const stateStatus = React.useMemo(() => { const {label, theme, icon, help} = getBridgePileStateInfo(pile.State); if (!label) { return null; } return ( {label} ); }, [pile.State]); const pileHealthcheck = React.useMemo( () => getBridgePileHealthcheck( pile.Name, leavesIssues, healthcheckAvailable, healthcheckSupportsPiles, ), [healthcheckAvailable, healthcheckSupportsPiles, leavesIssues, pile.Name], ); const pileName = pile.Name?.trim() || EMPTY_DATA_PLACEHOLDER; const nodes = pile.Nodes === undefined ? EMPTY_DATA_PLACEHOLDER : formatNumber(pile.Nodes); const handleHealthcheckClick = () => { if (pileHealthcheck.target) { onHealthcheckClick(pileHealthcheck.target); } }; return ( {pileName} {current ? ( ) : null} {stateStatus} {healthcheckLoading ? ( ) : ( ) : undefined } qa="bridge-pile-healthcheck" > {pileHealthcheck.target ? ( {i18n('label_bridge-pile-health-status-prefix', {pileName})} ) : null} )} {i18n('field_nodes')}: {nodes} ); }); export const BridgeInfoTable = React.memo(function BridgeInfoTable({ piles, database, clusterName, }: BridgeInfoTableProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); const {handleOpenHealthcheckIssue} = useClusterHealthcheckQueryParams(); const { currentData: healthcheckData, isFetching: healthcheckFetching, error: healthcheckError, } = healthcheckApi.useGetHealthcheckInfoQuery( {database: database ?? '', clusterName}, { pollingInterval: autoRefreshInterval, skip: !database, }, ); const leavesIssues = useTypedSelector((state) => selectLeavesIssues(state, database ?? '', clusterName), ); const healthcheckAvailable = healthcheckData !== undefined && healthcheckError === undefined; const healthcheckLoading = healthcheckData === undefined && healthcheckFetching; const currentPileName = healthcheckData?.location?.pile?.name; const healthcheckSupportsPiles = healthcheckData?.location?.pile !== undefined; const sortedPiles = React.useMemo(() => { return piles .map((pile, index) => ({pile, index})) .sort((left, right) => { const leftPrimary = left.pile.State === BridgePileState.PRIMARY; const rightPrimary = right.pile.State === BridgePileState.PRIMARY; if (leftPrimary !== rightPrimary) { return leftPrimary ? -1 : 1; } return left.index - right.index; }); }, [piles]); return ( {sortedPiles.map(({pile, index}) => ( ))} ); });