import React from 'react'; import {isNil} from 'lodash'; import {DiskBarLabel} from '../../../components/DiskStateProgressBar/DiskBarLabel'; import {DiskIndicator} from '../../../components/DiskStateProgressBar/DiskIndicator'; import {DiskStateProgressBar} from '../../../components/DiskStateProgressBar/DiskStateProgressBar'; import {PDiskAllModeIndicators} from '../../../components/DiskStateProgressBar/PDiskAllModeIndicators'; import {HoverPopup} from '../../../components/HoverPopup/HoverPopup'; import {InternalLink} from '../../../components/InternalLink'; import {PDiskPopup} from '../../../components/PDiskPopup/PDiskPopup'; import {getPDiskPagePath} from '../../../routes'; import {isCapacityAlert} from '../../../types/api/enums'; import {cn} from '../../../utils/cn'; import {EMPTY_DATA_PLACEHOLDER} from '../../../utils/constants'; import type { DiskIndicatorValue, PDiskDisplayState, PDiskDisplayStateGetter, } from '../../../utils/disks/displayState'; import {getDefaultPDiskDisplayState} from '../../../utils/disks/displayState'; import {getDiskBarTone} from '../../../utils/disks/getDiskBarTone'; import {formatPDiskType} from '../../../utils/disks/getPDiskType'; import {getPDiskId, getVDiskStatusIcon} from '../../../utils/disks/helpers'; import type {PreparedPDisk} from '../../../utils/disks/types'; import {isNumeric} from '../../../utils/utils'; import {DISKS_POPUP_DEBOUNCE_TIMEOUT} from '../shared'; import {i18n} from './i18n'; import './PDisk.scss'; const b = cn('pdisk-storage'); const EMPTY_ALL_MODE_INDICATORS = {}; interface PDiskNodeBarContentProps { left?: React.ReactNode; center?: React.ReactNode; type?: React.ReactNode; } function PDiskNodeBarContent({left, center, type}: PDiskNodeBarContentProps) { return (
{left}
{center}
{type}
); } interface GetPDiskContentParams { barContent: React.ReactNode; data: PreparedPDisk; hasIndicators: boolean; showAllocatedPercentLabel: boolean; showTypeLabel?: boolean; isNoData?: boolean; } function getPDiskContent({ barContent, data, hasIndicators, showAllocatedPercentLabel, showTypeLabel, isNoData, }: GetPDiskContentParams) { if (!showTypeLabel) { return barContent; } const showNoDataLabel = isNoData && !showAllocatedPercentLabel; return ( {showNoDataLabel ? barContent : null} {formatPDiskType(data.Type) || EMPTY_DATA_PLACEHOLDER} } /> ); } interface GetPDiskBarContentParams { allocatedPercent?: number; isAllMode: boolean; noDataPlaceholder?: React.ReactNode; showAllocatedPercentLabel?: boolean; showNoDataPlaceholder?: boolean; } function getPDiskBarContent({ allocatedPercent, isAllMode, noDataPlaceholder, showAllocatedPercentLabel, showNoDataPlaceholder, }: GetPDiskBarContentParams) { const hasAllocatedPercent = isNumeric(allocatedPercent) && allocatedPercent >= 0; if (hasAllocatedPercent && showAllocatedPercentLabel !== false) { return {`${Math.floor(allocatedPercent)}%`}; } if ( noDataPlaceholder && (!hasAllocatedPercent || showNoDataPlaceholder === true || isAllMode) ) { return {noDataPlaceholder}; } return null; } interface GetPDiskBarIndicatorParams { hidden: boolean; icon?: DiskIndicatorValue; placement: 'inline' | 'overlap'; severity: number; withIcon?: boolean; } function getPDiskBarIndicator({ hidden, icon, placement, severity, withIcon, }: GetPDiskBarIndicatorParams) { const resolvedIndicator = icon ?? getVDiskStatusIcon(severity, false); if (!withIcon || hidden || !resolvedIndicator) { return { leading: null, overflowVisible: false, showIndicator: false, }; } return { leading: , overflowVisible: placement === 'overlap', showIndicator: true, }; } function getAccessiblePDiskId(data: PreparedPDisk) { return ( data.StringifiedId || getPDiskId({nodeId: data.NodeId, pDiskId: data.PDiskId}) || i18n('context_no-data') ); } function getAllocatedPercentAccessibleName(allocatedPercent: number | undefined) { const hasAllocatedPercent = typeof allocatedPercent === 'number' && Number.isFinite(allocatedPercent) && allocatedPercent >= 0; return hasAllocatedPercent ? `${Math.floor(allocatedPercent)}%` : i18n('context_no-data'); } function getAllModeAccessibleName( data: PreparedPDisk, allocatedPercent: number | undefined, 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', ); return i18n('context_all-mode-accessible-name', { pdiskId: getAccessiblePDiskId(data), health, state: data.State || noData, capacityAlert: data.PDiskCapacityAlert || noData, drive: data.DriveStatus || noData, decommit: data.DecommitStatus || noData, maintenance: data.MaintenanceStatus || noData, device: data.Device || noData, realtime: data.Realtime || noData, allocatedPercent: getAllocatedPercentAccessibleName(allocatedPercent), }); } function getAccessibleName( data: PreparedPDisk, {mode, allMode, isNoData, allocatedPercent, driveType}: PDiskDisplayState, ) { if (!mode) { return undefined; } if (mode === 'all') { return getAllModeAccessibleName(data, allocatedPercent, allMode?.hasIssues); } const noData = i18n('context_no-data'); let diskName = i18n('context_pdisk', {pdiskId: getAccessiblePDiskId(data)}); if (isNoData) { diskName = i18n('context_pdisk-no-whiteboard', {disk: diskName, noData}); } const {State, Device, Realtime} = isNoData ? {} : data; 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: State || noData}); case 'space': return i18n('context_space-accessible-name', { disk: diskName, capacityAlert: isCapacityAlert(data.PDiskCapacityAlert) ? data.PDiskCapacityAlert : noData, allocatedPercent: getAllocatedPercentAccessibleName(allocatedPercent), }); case 'drive': return i18n('context_drive-accessible-name', { disk: diskName, drive: data.DriveStatus || noData, }); case 'decommit': return i18n('context_decommit-accessible-name', { disk: diskName, decommit: data.DecommitStatus || noData, }); case 'maintenance': return i18n('context_maintenance-accessible-name', { disk: diskName, maintenance: data.MaintenanceStatus || noData, }); case 'device': return i18n('context_device-accessible-name', { disk: diskName, device: Device || noData, realtime: Realtime || noData, }); default: return undefined; } } export interface PDiskProps { data?: PreparedPDisk; showPopup?: boolean; onShowPopup?: VoidFunction; onHidePopup?: VoidFunction; onClosePopup?: VoidFunction; className?: string; progressBarClassName?: string; // Nodes derive this width from their VDisk rows, overriding the Groups mode defaults. width?: number; delayOpen?: number; delayClose?: number; withIcon?: boolean; showTypeLabel?: boolean; inactive?: boolean; highlighted?: boolean; getDisplayState?: PDiskDisplayStateGetter; topContent?: React.ReactNode; } export const PDisk = ({ data = {}, showPopup, onShowPopup, onHidePopup, onClosePopup, className, progressBarClassName, width, delayOpen = DISKS_POPUP_DEBOUNCE_TIMEOUT, delayClose = DISKS_POPUP_DEBOUNCE_TIMEOUT, withIcon, showTypeLabel, inactive, highlighted, getDisplayState, topContent, }: PDiskProps) => { const {NodeId, PDiskId} = data; const pDiskIdsDefined = !isNil(NodeId) && !isNil(PDiskId); const anchorRef = React.useRef(null); const displayState = React.useMemo( () => (getDisplayState ?? getDefaultPDiskDisplayState)(data), [data, getDisplayState], ); const isAllMode = displayState.mode === 'all'; const noDataPlaceholder = displayState.showNoDataPlaceholder === false ? undefined : i18n('context_no-data'); const allocatedPercent = displayState.allocatedPercent; const hasAllocatedPercent = isNumeric(allocatedPercent) && allocatedPercent >= 0; const iconPlacement = displayState.iconPlacement ?? 'inline'; const accessibleName = getAccessibleName(data, displayState); const {leading, overflowVisible, showIndicator} = getPDiskBarIndicator({ hidden: Boolean(displayState.isLegendInactive), icon: displayState.icon, placement: iconPlacement, severity: displayState.severity, withIcon, }); const barContent = getPDiskBarContent({ allocatedPercent, isAllMode, noDataPlaceholder, showAllocatedPercentLabel: displayState.showAllocatedPercentLabel, showNoDataPlaceholder: displayState.showNoDataPlaceholder, }); const showAllocatedPercentLabel = hasAllocatedPercent && displayState.showAllocatedPercentLabel !== false; const allModeIndicators = displayState.allMode?.indicators ?? EMPTY_ALL_MODE_INDICATORS; const hasAllModeIndicators = isAllMode && Object.values(allModeIndicators).some(Boolean); const overlay = hasAllModeIndicators ? ( ) : null; const content = getPDiskContent({ barContent, data, hasIndicators: showIndicator || hasAllModeIndicators, showAllocatedPercentLabel, showTypeLabel, isNoData: displayState.isNoData, }); const tone = getDiskBarTone({ driveType: displayState.driveType, severity: displayState.severity, showIndicator, indicator: displayState.icon, isNoData: displayState.isNoData, }); let pDiskPath: string | undefined; if (pDiskIdsDefined) { pDiskPath = getPDiskPagePath(PDiskId, NodeId); } return (
{topContent} } delayOpen={delayOpen} delayClose={delayClose} > 0} strongFill={displayState.allMode?.hasIssues} borderless={displayState.borderless} overflowVisible={overflowVisible} />
); };