diff --git a/src/components/VDiskInfo/VDiskInfo.tsx b/src/components/VDiskInfo/VDiskInfo.tsx index 3013b521bb..9af4110136 100644 --- a/src/components/VDiskInfo/VDiskInfo.tsx +++ b/src/components/VDiskInfo/VDiskInfo.tsx @@ -3,15 +3,20 @@ import React from 'react'; import {Flex} from '@gravity-ui/uikit'; import {getVDiskPagePath} from '../../routes'; +import {EVDiskState} from '../../types/api/vdisk'; import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; -import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters'; +import { + formatStorageValuesToGb, + formatUptimeInSeconds, +} from '../../utils/dataFormatters/dataFormatters'; import {createVDiskDeveloperUILink} from '../../utils/developerUI/developerUI'; import {getSeverityColor} from '../../utils/disks/helpers'; import type {PreparedVDisk} from '../../utils/disks/types'; import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges'; import {bytesToSpeed} from '../../utils/utils'; import {InfoViewer} from '../InfoViewer'; +import {InternalLink} from '../InternalLink'; import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon'; import {ProgressViewer} from '../ProgressViewer/ProgressViewer'; import {StatusIcon} from '../StatusIcon/StatusIcon'; @@ -46,6 +51,9 @@ export function VDiskInfo({ FrontQueues, Guid, Replicated, + ReplicationProgress, + ReplicationSecondsRemaining, + Donors, VDiskState, VDiskSlotId, Kind, @@ -130,6 +138,31 @@ export function VDiskInfo({ value: Replicated ? vDiskInfoKeyset('yes') : vDiskInfoKeyset('no'), }); } + // Only show replication progress and time remaining when disk is not replicated and state is OK + if (Replicated === false && VDiskState === EVDiskState.OK) { + if (valueIsDefined(ReplicationProgress)) { + rightColumn.push({ + label: vDiskInfoKeyset('replication-progress'), + value: ( + + ), + }); + } + if (valueIsDefined(ReplicationSecondsRemaining)) { + const timeRemaining = formatUptimeInSeconds(ReplicationSecondsRemaining); + if (timeRemaining) { + rightColumn.push({ + label: vDiskInfoKeyset('replication-time-remaining'), + value: timeRemaining, + }); + } + } + } if (valueIsDefined(VDiskSlotId)) { rightColumn.push({label: vDiskInfoKeyset('slot-id'), value: VDiskSlotId}); } @@ -153,6 +186,45 @@ export function VDiskInfo({ }); } + // Show donors list when replication is in progress + if (Replicated === false && VDiskState === EVDiskState.OK && Donors?.length) { + const donorLinks = Donors.map((donor, index) => { + const { + StringifiedId: id, + NodeId: dNodeId, + PDiskId: dPDiskId, + VDiskSlotId: dVSlotId, + } = donor; + + if (!id || !dVSlotId || !dNodeId || !dPDiskId) { + return null; + } + + const vDiskPath = getVDiskPagePath({ + nodeId: dNodeId, + pDiskId: dPDiskId, + vDiskSlotId: dVSlotId, + }); + + return ( + + {id} + + ); + }).filter(Boolean); + + if (donorLinks.length) { + rightColumn.push({ + label: vDiskInfoKeyset('donors'), + value: ( + + {donorLinks} + + ), + }); + } + } + const diskParamsDefined = valueIsDefined(PDiskId) && valueIsDefined(NodeId) && valueIsDefined(VDiskSlotId); diff --git a/src/components/VDiskInfo/i18n/en.json b/src/components/VDiskInfo/i18n/en.json index e87bf2694b..6062b3736d 100644 --- a/src/components/VDiskInfo/i18n/en.json +++ b/src/components/VDiskInfo/i18n/en.json @@ -8,6 +8,9 @@ "instance-guid": "Instance GUID", "replication-status": "Replicated", + "replication-progress": "Replication Progress", + "replication-time-remaining": "Time Remaining", + "donors": "Donors", "state-status": "VDisk State", "space-status": "Disk Space", diff --git a/src/components/VDiskPopup/VDiskPopup.tsx b/src/components/VDiskPopup/VDiskPopup.tsx index 84199c3426..ace30002ec 100644 --- a/src/components/VDiskPopup/VDiskPopup.tsx +++ b/src/components/VDiskPopup/VDiskPopup.tsx @@ -4,9 +4,11 @@ import {Flex, Label} from '@gravity-ui/uikit'; import {selectNodesMap} from '../../store/reducers/nodesList'; import {EFlag} from '../../types/api/enums'; +import {EVDiskState} from '../../types/api/vdisk'; import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; +import {formatUptimeInSeconds} from '../../utils/dataFormatters/dataFormatters'; import {createVDiskDeveloperUILink} from '../../utils/developerUI/developerUI'; import {isFullVDiskData} from '../../utils/disks/helpers'; import type {PreparedVDisk, UnavailableDonor} from '../../utils/disks/types'; @@ -73,6 +75,8 @@ const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => DiskSpace, FrontQueues, Replicated, + ReplicationProgress, + ReplicationSecondsRemaining, UnsyncedVDisks, AllocatedSize, ReadThroughput, @@ -125,8 +129,27 @@ const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => vdiskData.push({label: 'FrontQueues', value: FrontQueues}); } - if (Replicated === false) { + if (Replicated === false && VDiskState === EVDiskState.OK) { vdiskData.push({label: 'Replicated', value: 'NO'}); + + // Only show replication progress and time remaining when disk is not replicated and state is OK + if (valueIsDefined(ReplicationProgress)) { + const progressPercent = Math.round(ReplicationProgress * 100); + vdiskData.push({ + label: 'Progress', + value: `${progressPercent}%`, + }); + } + + if (valueIsDefined(ReplicationSecondsRemaining)) { + const timeRemaining = formatUptimeInSeconds(ReplicationSecondsRemaining); + if (timeRemaining) { + vdiskData.push({ + label: 'Remaining', + value: timeRemaining, + }); + } + } } if (UnsyncedVDisks) { diff --git a/src/utils/disks/prepareDisks.ts b/src/utils/disks/prepareDisks.ts index f77bc75555..efe4d24ccc 100644 --- a/src/utils/disks/prepareDisks.ts +++ b/src/utils/disks/prepareDisks.ts @@ -1,3 +1,5 @@ +import {isNil} from 'lodash'; + import {valueIsDefined} from '..'; import type {TPDiskStateInfo} from '../../types/api/pdisk'; import type {TVDiskStateInfo, TVSlotId} from '../../types/api/vdisk'; @@ -63,7 +65,26 @@ export function prepareWhiteboardVDiskData( const StringifiedId = stringifyVdiskId(VDiskId); const preparedDonors = Donors?.map((donor) => { - return prepareWhiteboardVDiskData({...donor, DonorMode: true}); + // Handle both TVDiskStateInfo and TVSlotId donor types + if (isFullVDiskData(donor)) { + // Full VDisk data + return prepareWhiteboardVDiskData({...donor, DonorMode: true}); + } else { + // TVSlotId data - create a minimal PreparedVDisk + const {NodeId: dNodeId, PDiskId: dPDiskId, VSlotId: vSlotId} = donor; + const stringifiedId = + !isNil(dNodeId) && !isNil(dPDiskId) && !isNil(vSlotId) + ? `${dNodeId}-${dPDiskId}-${vSlotId}` + : ''; + + return { + NodeId: dNodeId, + PDiskId: dPDiskId, + VDiskSlotId: vSlotId, + StringifiedId: stringifiedId, + DonorMode: true, + }; + } }); return {