diff --git a/src/components/HoverPopup/HoverPopup.tsx b/src/components/HoverPopup/HoverPopup.tsx index 6b6c541fb..4fc0165c6 100644 --- a/src/components/HoverPopup/HoverPopup.tsx +++ b/src/components/HoverPopup/HoverPopup.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import type {PopupProps} from '@gravity-ui/uikit'; import {Popup} from '@gravity-ui/uikit'; import debounce from 'lodash/debounce'; @@ -11,7 +12,7 @@ const b = cn('hover-popup'); const DEBOUNCE_TIMEOUT = 100; -interface HoverPopupProps { +type HoverPopupProps = { children: React.ReactNode; popupContent: React.ReactNode; showPopup?: boolean; @@ -19,7 +20,7 @@ interface HoverPopupProps { anchorRef?: React.RefObject; onShowPopup?: VoidFunction; onHidePopup?: VoidFunction; -} +} & Pick; export const HoverPopup = ({ children, @@ -29,6 +30,8 @@ export const HoverPopup = ({ anchorRef, onShowPopup, onHidePopup, + placement = ['top', 'bottom'], + contentClassName, }: HoverPopupProps) => { const [isPopupVisible, setIsPopupVisible] = React.useState(false); const anchor = React.useRef(null); @@ -88,18 +91,18 @@ export const HoverPopup = ({ return ( -
+ {children} -
+ { +export const preparePDiskData = ( + data: PreparedPDisk, + nodeHost?: string, + withDeveloperUILink?: boolean, +) => { const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Type, Device} = data; const pdiskData: InfoViewerItem[] = [ @@ -50,6 +57,18 @@ export const preparePDiskData = (data: PreparedPDisk, nodeHost?: string) => { pdiskData.push({label: 'Device', value: Device}); } + if (withDeveloperUILink && valueIsDefined(NodeId) && valueIsDefined(PDiskId)) { + const pDiskInternalViewerPath = createPDiskDeveloperUILink({ + nodeId: NodeId, + pDiskId: PDiskId, + }); + + pdiskData.push({ + label: 'Links', + value: , + }); + } + return pdiskData; }; @@ -58,9 +77,13 @@ interface PDiskPopupProps { } export const PDiskPopup = ({data}: PDiskPopupProps) => { + const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges); const nodeHostsMap = useTypedSelector(selectNodeHostsMap); const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined; - const info = React.useMemo(() => preparePDiskData(data, nodeHost), [data, nodeHost]); + const info = React.useMemo( + () => preparePDiskData(data, nodeHost, isUserAllowedToMakeChanges), + [data, nodeHost, isUserAllowedToMakeChanges], + ); return ; }; diff --git a/src/components/VDiskPopup/VDiskPopup.tsx b/src/components/VDiskPopup/VDiskPopup.tsx index ca09c7770..74314044c 100644 --- a/src/components/VDiskPopup/VDiskPopup.tsx +++ b/src/components/VDiskPopup/VDiskPopup.tsx @@ -2,12 +2,14 @@ import React from 'react'; import {Label} from '@gravity-ui/uikit'; +import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication'; import {selectNodeHostsMap} from '../../store/reducers/nodesList'; import {EFlag} from '../../types/api/enums'; import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters'; +import {createVDiskDeveloperUILink} from '../../utils/developerUI/developerUI'; import {isFullVDiskData} from '../../utils/disks/helpers'; import type {PreparedVDisk, UnavailableDonor} from '../../utils/disks/types'; import {useTypedSelector} from '../../utils/hooks'; @@ -15,6 +17,7 @@ import {bytesToGB, bytesToSpeed} from '../../utils/utils'; import type {InfoViewerItem} from '../InfoViewer'; import {InfoViewer} from '../InfoViewer'; import {InternalLink} from '../InternalLink'; +import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon'; import {preparePDiskData} from '../PDiskPopup/PDiskPopup'; import {getVDiskLink} from '../VDisk/utils'; @@ -22,7 +25,7 @@ import './VDiskPopup.scss'; const b = cn('vdisk-storage-popup'); -const prepareUnavailableVDiskData = (data: UnavailableDonor) => { +const prepareUnavailableVDiskData = (data: UnavailableDonor, withDeveloperUILink?: boolean) => { const {NodeId, PDiskId, VSlotId, StoragePoolName} = data; const vdiskData: InfoViewerItem[] = [{label: 'State', value: 'not available'}]; @@ -37,11 +40,33 @@ const prepareUnavailableVDiskData = (data: UnavailableDonor) => { {label: 'VSlotId', value: VSlotId ?? EMPTY_DATA_PLACEHOLDER}, ); + if ( + withDeveloperUILink && + valueIsDefined(NodeId) && + valueIsDefined(PDiskId) && + valueIsDefined(VSlotId) + ) { + const vDiskInternalViewerPath = createVDiskDeveloperUILink({ + nodeId: NodeId, + pDiskId: PDiskId, + vDiskSlotId: VSlotId, + }); + + vdiskData.push({ + label: 'Links', + value: , + }); + } + return vdiskData; }; -const prepareVDiskData = (data: PreparedVDisk) => { +// eslint-disable-next-line complexity +const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => { const { + NodeId, + PDiskId, + VDiskSlotId, StringifiedId, VDiskState, SatisfactionRank, @@ -126,6 +151,24 @@ const prepareVDiskData = (data: PreparedVDisk) => { }); } + if ( + withDeveloperUILink && + valueIsDefined(NodeId) && + valueIsDefined(PDiskId) && + valueIsDefined(VDiskSlotId) + ) { + const vDiskInternalViewerPath = createVDiskDeveloperUILink({ + nodeId: NodeId, + pDiskId: PDiskId, + vDiskSlotId: VDiskSlotId, + }); + + vdiskData.push({ + label: 'Links', + value: , + }); + } + return vdiskData; }; @@ -136,16 +179,24 @@ interface VDiskPopupProps { export const VDiskPopup = ({data}: VDiskPopupProps) => { const isFullData = isFullVDiskData(data); + const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges); + const vdiskInfo = React.useMemo( - () => (isFullData ? prepareVDiskData(data) : prepareUnavailableVDiskData(data)), - [data, isFullData], + () => + isFullData + ? prepareVDiskData(data, isUserAllowedToMakeChanges) + : prepareUnavailableVDiskData(data, isUserAllowedToMakeChanges), + [data, isFullData, isUserAllowedToMakeChanges], ); const nodeHostsMap = useTypedSelector(selectNodeHostsMap); const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined; const pdiskInfo = React.useMemo( - () => isFullData && data.PDisk && preparePDiskData(data.PDisk, nodeHost), - [data, nodeHost, isFullData], + () => + isFullData && + data.PDisk && + preparePDiskData(data.PDisk, nodeHost, isUserAllowedToMakeChanges), + [data, nodeHost, isFullData, isUserAllowedToMakeChanges], ); const donorsInfo: InfoViewerItem[] = []; diff --git a/src/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.tsx b/src/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.tsx index fd460692f..c0e50afda 100644 --- a/src/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.tsx +++ b/src/containers/PDiskPage/PDiskSpaceDistribution/PDiskSpaceDistribution.tsx @@ -1,5 +1,5 @@ -import {ContentWithPopup} from '../../../components/ContentWithPopup/ContentWithPopup'; import {DiskStateProgressBar} from '../../../components/DiskStateProgressBar/DiskStateProgressBar'; +import {HoverPopup} from '../../../components/HoverPopup/HoverPopup'; import type {InfoViewerItem} from '../../../components/InfoViewer'; import {InfoViewer} from '../../../components/InfoViewer'; import {InternalLink} from '../../../components/InternalLink'; @@ -85,8 +85,8 @@ function Slot({item, pDiskId, nodeId}: SlotProps) { : undefined; return ( - } + } contentClassName={b('vdisk-popup')} placement={['right', 'top']} > @@ -105,13 +105,13 @@ function Slot({item, pDiskId, nodeId}: SlotProps) { } /> - + ); } if (isLogSlot(item)) { return ( - } + } contentClassName={b('vdisk-popup')} placement={['right', 'top']} > @@ -127,14 +127,14 @@ function Slot({item, pDiskId, nodeId}: SlotProps) { /> } /> - + ); } if (isEmptySlot(item)) { return ( - } + } contentClassName={b('vdisk-popup')} placement={['right', 'top']} > @@ -150,7 +150,7 @@ function Slot({item, pDiskId, nodeId}: SlotProps) { /> } /> - + ); } diff --git a/src/store/reducers/pdisk/utils.ts b/src/store/reducers/pdisk/utils.ts index dadc74eca..f493a190d 100644 --- a/src/store/reducers/pdisk/utils.ts +++ b/src/store/reducers/pdisk/utils.ts @@ -10,6 +10,9 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [ TPDiskInfoResponse, TEvSystemStateResponse, ]): PDiskData { + const rawNode = nodeResponse.SystemStateInfo?.[0]; + const preparedNode = prepareNodeSystemState(rawNode); + const {BSC = {}, Whiteboard = {}} = pdiskResponse || {}; const {PDisk: WhiteboardPDiskData = {}, VDisks: WhiteboardVDisksData = []} = Whiteboard; @@ -17,6 +20,8 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [ const preparedPDisk = preparePDiskData(WhiteboardPDiskData, BSCPDiskData); + const NodeId = preparedPDisk.NodeId ?? preparedNode.NodeId; + const { LogUsedSize, LogTotalSize, @@ -43,9 +48,8 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [ }; } - const preparedVDisks = WhiteboardVDisksData.map(prepareVDiskData).sort( - (disk1, disk2) => Number(disk2.VDiskSlotId) - Number(disk1.VDiskSlotId), - ); + const preparedVDisks = WhiteboardVDisksData.map((disk) => prepareVDiskData({...disk, NodeId})); + preparedVDisks.sort((disk1, disk2) => Number(disk2.VDiskSlotId) - Number(disk1.VDiskSlotId)); const vdisksSlots: SlotItem<'vDisk'>[] = preparedVDisks.map((preparedVDisk) => { return { @@ -94,12 +98,9 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [ diskSlots.unshift(logSlot); } - const rawNode = nodeResponse.SystemStateInfo?.[0]; - const preparedNode = prepareNodeSystemState(rawNode); - return { ...preparedPDisk, - NodeId: preparedPDisk.NodeId ?? preparedNode.NodeId, + NodeId, NodeHost: preparedNode.Host, NodeType: preparedNode.Roles?.[0], NodeDC: preparedNode.DC,