import React from 'react'; import type {PopupPlacement, PopupProps} from '@gravity-ui/uikit'; import {useVDiskPagePath} from '../../routes'; import {isCapacityAlert} from '../../types/api/enums'; import {cn} from '../../utils/cn'; import {NOT_AVAILABLE_SEVERITY} from '../../utils/disks/constants'; import type { DiskIndicatorValue, VDiskDisplayState, VDiskDisplayStateGetter, } from '../../utils/disks/displayState'; import {getDefaultDiskDisplayState} from '../../utils/disks/displayState'; import {getDiskBarTone} from '../../utils/disks/getDiskBarTone'; import {formatPDiskType} from '../../utils/disks/getPDiskType'; import {getVDiskStatusIcon} from '../../utils/disks/helpers'; import type {PreparedVDisk} from '../../utils/disks/types'; import {isNumeric} from '../../utils/utils'; import {AllModeIndicators} from '../DiskStateProgressBar/AllModeIndicators'; import {DiskBarLabel} from '../DiskStateProgressBar/DiskBarLabel'; import {DiskIndicator} from '../DiskStateProgressBar/DiskIndicator'; import {DiskStateProgressBar} from '../DiskStateProgressBar/DiskStateProgressBar'; import {HoverPopup} from '../HoverPopup/HoverPopup'; import {InternalLink} from '../InternalLink'; import {VDiskPopup} from '../VDiskPopup/VDiskPopup'; import {getFlagStatusText} from './getFlagStatusText'; import {i18n} from './i18n'; import './VDisk.scss'; const b = cn('ydb-vdisk-component'); const DEFAULT_POPUP_OFFSET: PopupProps['offset'] = {mainAxis: 2, crossAxis: 0}; const EMPTY_ALL_MODE_INDICATORS = {}; interface GetVDiskBarContentParams { allocatedPercent?: number; compact?: boolean; noDataPlaceholder?: React.ReactNode; severity: number; showAllocatedPercentLabel?: boolean; showNoDataPlaceholder?: boolean; } function getVDiskBarContent({ allocatedPercent, compact, noDataPlaceholder, severity, showAllocatedPercentLabel, showNoDataPlaceholder, }: GetVDiskBarContentParams) { const hasAllocatedPercent = isNumeric(allocatedPercent) && allocatedPercent >= 0; if (!compact && hasAllocatedPercent && showAllocatedPercentLabel !== false) { return {`${Math.floor(allocatedPercent)}%`}; } if (!compact && (!hasAllocatedPercent || showNoDataPlaceholder === true) && noDataPlaceholder) { return {noDataPlaceholder}; } if (compact && severity === NOT_AVAILABLE_SEVERITY && noDataPlaceholder) { return {noDataPlaceholder}; } return null; } interface GetVDiskBarIndicatorParams { hidden: boolean; icon?: DiskIndicatorValue; indicatorClassName?: string; iconGroupSize?: number; iconSize?: number; isDonor?: boolean; placement: 'inline' | 'overlap'; severity: number; withIcon?: boolean; } function getVDiskBarIndicator({ hidden, icon, indicatorClassName, iconGroupSize, iconSize, isDonor, placement, severity, withIcon, }: GetVDiskBarIndicatorParams) { const resolvedIndicator = icon ?? getVDiskStatusIcon(severity, isDonor); if (!withIcon || hidden || !resolvedIndicator) { return {leading: null, overflowVisible: false, showIndicator: false}; } return { leading: ( ), overflowVisible: placement === 'overlap', showIndicator: true, }; } function getReplicationAccessibleName(replicated: boolean | undefined) { if (replicated === undefined) { return i18n('context_no-data'); } return i18n( replicated ? 'context_all-mode-replication-complete' : 'context_all-mode-replication-in-progress', ); } function getAllModeAccessibleName(data: PreparedVDisk, hasIssues: boolean | undefined) { const noData = i18n('context_no-data'); const health = hasIssues === undefined ? noData : i18n( hasIssues ? 'context_all-mode-health-issues' : 'context_all-mode-health-healthy', ); const allocatedPercent = Number.isFinite(data.AllocatedPercent) && Number(data.AllocatedPercent) >= 0 ? `${data.AllocatedPercent}%` : noData; return i18n('context_all-mode-accessible-name', { vdiskId: data.StringifiedId || noData, health, state: data.VDiskState || data.Status || noData, replication: getReplicationAccessibleName(data.Replicated), capacityAlert: data.CapacityAlert || noData, frontQueues: data.FrontQueues || noData, freshCompaction: data.SatisfactionRank?.FreshRank?.Flag || noData, levelCompaction: data.SatisfactionRank?.LevelRank?.Flag || noData, allocatedPercent, }); } function getAccessibleDiskName( data: PreparedVDisk, {mode, isNoData}: Pick, ) { const noData = i18n('context_no-data'); const diskName = i18n(data.DonorMode ? 'context_donor-vdisk' : 'context_vdisk', { vdiskId: data.StringifiedId || noData, nodeId: data.NodeId ?? noData, }); if (!isNoData) { return diskName; } const hasVDiskWhiteboardData = data.HasWhiteboardData ?? Boolean(data.VDiskId); return i18n( mode === 'driveType' && hasVDiskWhiteboardData ? 'context_pdisk-no-whiteboard' : 'context_vdisk-no-whiteboard', {disk: diskName, noData}, ); } function getAccessibleName( data: PreparedVDisk, {mode, allMode, isNoData, driveType}: VDiskDisplayState, ) { if (!mode) { return getAccessibleDiskName(data, {mode, isNoData}); } if (mode === 'all') { return getAllModeAccessibleName(data, allMode?.hasIssues); } const noData = i18n('context_no-data'); const diskName = getAccessibleDiskName(data, {mode, isNoData}); const {CapacityAlert, FrontQueues, SatisfactionRank, Replicated} = isNoData ? {} : data; const {FreshRank, LevelRank} = SatisfactionRank ?? {}; switch (mode) { case 'driveType': return i18n('context_drive-type-accessible-name', { disk: diskName, driveType: formatPDiskType(driveType) || noData, }); case 'state': return i18n('context_state-accessible-name', { disk: diskName, state: data.VDiskState || data.Status || noData, replication: getReplicationAccessibleName(Replicated), }); case 'space': return i18n('context_space-accessible-name', { disk: diskName, capacityAlert: isCapacityAlert(CapacityAlert) ? CapacityAlert : noData, }); case 'frontQueues': return i18n('context_front-queues-accessible-name', { disk: diskName, frontQueues: getFlagStatusText(FrontQueues), }); case 'compaction': return i18n('context_compaction-accessible-name', { disk: diskName, freshCompaction: getFlagStatusText(FreshRank?.Flag), levelCompaction: getFlagStatusText(LevelRank?.Flag), }); default: return undefined; } } export interface VDiskProps { data?: PreparedVDisk; compact?: boolean; allModeSize?: 's' | 'm'; inactive?: boolean; showPopup?: boolean; onShowPopup?: VoidFunction; onHidePopup?: VoidFunction; onClosePopup?: VoidFunction; progressBarClassName?: string; delayOpen?: number; delayClose?: number; withIcon?: boolean; iconSize?: number; iconGroupSize?: number; indicatorClassName?: string; highlighted?: boolean; placement?: PopupPlacement; popupOffset?: PopupProps['offset']; withOpaqueBackground?: boolean; getDisplayState?: VDiskDisplayStateGetter; hidePDiskInPopup?: boolean; } export const VDisk = ({ data = {}, compact, allModeSize, inactive, showPopup, onShowPopup, onHidePopup, onClosePopup, progressBarClassName, delayClose, delayOpen, withIcon, iconSize, iconGroupSize, indicatorClassName, highlighted, placement = ['top', 'bottom', 'left', 'right'], popupOffset = DEFAULT_POPUP_OFFSET, withOpaqueBackground, getDisplayState, hidePDiskInPopup, }: VDiskProps) => { const getVDiskLink = useVDiskPagePath(); const vDiskPath = getVDiskLink({nodeId: data.NodeId, vDiskId: data.StringifiedId}); const isDonor = data.DonorMode; const displayState = React.useMemo( () => (getDisplayState ?? getDefaultDiskDisplayState)(data, isDonor), [data, getDisplayState, isDonor], ); const { severity, icon, mode, isLegendInactive, borderless, showNoDataPlaceholder, allocatedPercent, showAllocatedPercentLabel, striped, iconPlacement, allMode, isNoData, } = displayState; const isAllMode = mode === 'all'; const accessibleName = getAccessibleName(data, displayState); const {leading, overflowVisible, showIndicator} = getVDiskBarIndicator({ hidden: Boolean(isLegendInactive && !isDonor), icon, indicatorClassName, iconGroupSize, iconSize, isDonor: mode !== 'driveType' && isDonor, placement: iconPlacement, severity, withIcon, }); const noDataPlaceholder = showNoDataPlaceholder === false ? undefined : i18n('context_no-data'); const barContent = getVDiskBarContent({ allocatedPercent, compact, noDataPlaceholder, severity, showAllocatedPercentLabel, showNoDataPlaceholder, }); const overlay = !compact && isAllMode && !isNoData ? ( ) : null; const tone = getDiskBarTone({ driveType: displayState.driveType, severity, isDonor: mode !== 'driveType' && isDonor, showIndicator, indicator: icon, isNoData, }); return ( ( )} offset={popupOffset} delayClose={delayClose} delayOpen={delayOpen} // Allow all placement options, component should choose first available placement={placement} >
); };