diff --git a/eslint.config.mjs b/eslint.config.mjs index 68ea70c103..562b0a6951 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -46,6 +46,7 @@ export default [ 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], curly: ['error', 'all'], 'react-hooks/exhaustive-deps': 'warn', + 'react-hooks/rules-of-hooks': 'error', }, }, // TypeScript-specific rules that require type information diff --git a/src/components/ErrorBoundary/utils.tsx b/src/components/ErrorBoundary/utils.tsx index 8c2367cae2..da43b13a44 100644 --- a/src/components/ErrorBoundary/utils.tsx +++ b/src/components/ErrorBoundary/utils.tsx @@ -24,7 +24,7 @@ async function getBackendVersion() { // node_id=. returns data about node that fullfills request // normally this request should be fast (200-300ms with good connection) // timeout=1000 in order not to wait too much in case everything is broken - const data = await window.api.viewer.getNodeInfo('.', {timeout: 1000}); + const data = await window.api.viewer.getNodeInfo({nodeId: '.'}, {timeout: 1000}); return data?.SystemStateInfo?.[0]?.Version; } catch (error) { return {error: prepareErrorMessage(error)}; diff --git a/src/components/NodeId/NodeId.tsx b/src/components/NodeId/NodeId.tsx new file mode 100644 index 0000000000..d5028478a9 --- /dev/null +++ b/src/components/NodeId/NodeId.tsx @@ -0,0 +1,12 @@ +import {getDefaultNodePath} from '../../containers/Node/NodePages'; +import {useDatabaseFromQuery} from '../../utils/hooks/useDatabaseFromQuery'; +import {InternalLink} from '../InternalLink'; + +interface NodeIdProps { + id: string | number; +} + +export function NodeId({id}: NodeIdProps) { + const database = useDatabaseFromQuery(); + return {id}; +} diff --git a/src/components/PDiskPopup/PDiskPopup.tsx b/src/components/PDiskPopup/PDiskPopup.tsx index dcc1025c8f..aff71675df 100644 --- a/src/components/PDiskPopup/PDiskPopup.tsx +++ b/src/components/PDiskPopup/PDiskPopup.tsx @@ -10,6 +10,7 @@ import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; import {createPDiskDeveloperUILink} from '../../utils/developerUI/developerUI'; import type {PreparedPDisk} from '../../utils/disks/types'; import {useTypedSelector} from '../../utils/hooks'; +import {useDatabaseFromQuery} from '../../utils/hooks/useDatabaseFromQuery'; import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges'; import {bytesToGB, isNumeric} from '../../utils/utils'; import {InfoViewer} from '../InfoViewer'; @@ -109,8 +110,9 @@ interface PDiskPopupProps { } export const PDiskPopup = ({data}: PDiskPopupProps) => { + const database = useDatabaseFromQuery(); const isUserAllowedToMakeChanges = useIsUserAllowedToMakeChanges(); - const nodesMap = useTypedSelector(selectNodesMap); + const nodesMap = useTypedSelector((state) => selectNodesMap(state, database)); const nodeData = valueIsDefined(data.NodeId) ? nodesMap?.get(data.NodeId) : undefined; const info = React.useMemo( () => preparePDiskData(data, nodeData, isUserAllowedToMakeChanges), diff --git a/src/components/ProgressWrapper/SingleProgress.tsx b/src/components/ProgressWrapper/SingleProgress.tsx index 8ed36166b9..1f5e8fa1c5 100644 --- a/src/components/ProgressWrapper/SingleProgress.tsx +++ b/src/components/ProgressWrapper/SingleProgress.tsx @@ -25,10 +25,6 @@ export function SingleProgress({ size = PROGRESS_SIZE, withCapacityUsage = false, }: ProgressWrapperSingleProps) { - if (!isValidValue(value)) { - return
{i18n('alert_no-data')}
; - } - const numericValue = safeParseNumber(value); const numericCapacity = safeParseNumber(capacity); const clampedFillWidth = calculateProgressWidth(numericValue, numericCapacity); @@ -41,6 +37,10 @@ export function SingleProgress({ return formatProgressText(valueText, capacityText, numericCapacity); }, [valueText, capacityText, numericCapacity]); + if (!isValidValue(value)) { + return
{i18n('alert_no-data')}
; + } + return ( !segment.isInfo && segment.value > 0); }, [stack]); - if (displaySegments.length === 0) { - return
{i18n('alert_no-data')}
; - } - const totalValue = React.useMemo(() => { return displaySegments.reduce((sum, segment) => sum + segment.value, 0); }, [displaySegments]); @@ -59,6 +55,10 @@ export function StackProgress({ return formatProgressText(totalValueText, totalCapacityText, numericTotalCapacity || 0); }, [totalValueText, totalCapacityText, numericTotalCapacity]); + if (displaySegments.length === 0) { + return
{i18n('alert_no-data')}
; + } + return ( { align: DataTable.RIGHT, }; }; -export const getTabletIdColumn: GetShardsColumn = () => { +export const getTabletIdColumn: GetShardsColumn = ({database}) => { return { name: TOP_SHARDS_COLUMNS_IDS.TabletId, header: TOP_SHARDS_COLUMNS_TITLES.TabletId, @@ -51,6 +50,7 @@ export const getTabletIdColumn: GetShardsColumn = () => { ); }, @@ -65,7 +65,7 @@ export const getNodeIdColumn: GetShardsColumn = () => { if (!row.NodeId) { return EMPTY_DATA_PLACEHOLDER; } - return {row.NodeId}; + return ; }, align: DataTable.RIGHT, }; diff --git a/src/components/VDisk/VDisk.tsx b/src/components/VDisk/VDisk.tsx index e45bef427d..235f8e9d8e 100644 --- a/src/components/VDisk/VDisk.tsx +++ b/src/components/VDisk/VDisk.tsx @@ -1,5 +1,6 @@ import {cn} from '../../utils/cn'; import type {PreparedVDisk} from '../../utils/disks/types'; +import {useDatabaseFromQuery} from '../../utils/hooks/useDatabaseFromQuery'; import {DiskStateProgressBar} from '../DiskStateProgressBar/DiskStateProgressBar'; import {HoverPopup} from '../HoverPopup/HoverPopup'; import {InternalLink} from '../InternalLink'; @@ -34,7 +35,10 @@ export const VDisk = ({ delayClose, delayOpen, }: VDiskProps) => { - const vDiskPath = getVDiskLink(data); + const database = useDatabaseFromQuery(); + const vDiskPath = getVDiskLink(data, { + database: database, + }); return ( ({ wrap, }: VDiskInfoProps) { const isUserAllowedToMakeChanges = useIsUserAllowedToMakeChanges(); + const database = useDatabaseFromQuery(); const { AllocatedSize, @@ -66,15 +68,16 @@ export function VDiskInfo({ ReadThroughput, WriteThroughput, PDiskId, + StringifiedId, NodeId, } = data || {}; const leftColumn = []; - if (valueIsDefined(StoragePoolName)) { + if (!isNil(StoragePoolName)) { leftColumn.push({label: vDiskInfoKeyset('pool-name'), value: StoragePoolName}); } - if (valueIsDefined(VDiskState)) { + if (!isNil(VDiskState)) { leftColumn.push({ label: vDiskInfoKeyset('state-status'), value: VDiskState, @@ -93,37 +96,37 @@ export function VDiskInfo({ ), }); } - if (valueIsDefined(DiskSpace)) { + if (!isNil(DiskSpace)) { leftColumn.push({ label: vDiskInfoKeyset('space-status'), value: , }); } - if (valueIsDefined(FrontQueues)) { + if (!isNil(FrontQueues)) { leftColumn.push({ label: vDiskInfoKeyset('front-queues'), value: , }); } - if (valueIsDefined(SatisfactionRank?.FreshRank?.Flag)) { + if (!isNil(SatisfactionRank?.FreshRank?.Flag)) { leftColumn.push({ label: vDiskInfoKeyset('fresh-rank-satisfaction'), value: , }); } - if (valueIsDefined(SatisfactionRank?.LevelRank?.Flag)) { + if (!isNil(SatisfactionRank?.LevelRank?.Flag)) { leftColumn.push({ label: vDiskInfoKeyset('level-rank-satisfaction'), value: , }); } - if (valueIsDefined(ReadThroughput)) { + if (!isNil(ReadThroughput)) { leftColumn.push({ label: vDiskInfoKeyset('read-throughput'), value: bytesToSpeed(ReadThroughput), }); } - if (valueIsDefined(WriteThroughput)) { + if (!isNil(WriteThroughput)) { leftColumn.push({ label: vDiskInfoKeyset('write-throughput'), value: bytesToSpeed(WriteThroughput), @@ -132,7 +135,7 @@ export function VDiskInfo({ const rightColumn = []; - if (valueIsDefined(Replicated)) { + if (!isNil(Replicated)) { rightColumn.push({ label: vDiskInfoKeyset('replication-status'), value: Replicated ? vDiskInfoKeyset('yes') : vDiskInfoKeyset('no'), @@ -140,7 +143,7 @@ export function VDiskInfo({ } // 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)) { + if (!isNil(ReplicationProgress)) { rightColumn.push({ label: vDiskInfoKeyset('replication-progress'), value: ( @@ -153,7 +156,7 @@ export function VDiskInfo({ ), }); } - if (valueIsDefined(ReplicationSecondsRemaining)) { + if (!isNil(ReplicationSecondsRemaining)) { const timeRemaining = formatUptimeInSeconds(ReplicationSecondsRemaining); if (timeRemaining) { rightColumn.push({ @@ -163,23 +166,33 @@ export function VDiskInfo({ } } } - if (valueIsDefined(VDiskSlotId)) { + if (!isNil(VDiskSlotId)) { rightColumn.push({label: vDiskInfoKeyset('slot-id'), value: VDiskSlotId}); } + if (!isNil(PDiskId)) { + const pDiskPath = !isNil(NodeId) ? getPDiskPagePath(PDiskId, NodeId) : undefined; - if (valueIsDefined(Kind)) { + const value = pDiskPath ? {PDiskId} : PDiskId; + + rightColumn.push({ + label: vDiskInfoKeyset('label_pdisk-id'), + value, + }); + } + + if (!isNil(Kind)) { rightColumn.push({label: vDiskInfoKeyset('kind'), value: Kind}); } - if (valueIsDefined(Guid)) { + if (!isNil(Guid)) { rightColumn.push({label: vDiskInfoKeyset('guid'), value: Guid}); } - if (valueIsDefined(IncarnationGuid)) { + if (!isNil(IncarnationGuid)) { rightColumn.push({label: vDiskInfoKeyset('incarnation-guid'), value: IncarnationGuid}); } - if (valueIsDefined(InstanceGuid)) { + if (!isNil(InstanceGuid)) { rightColumn.push({label: vDiskInfoKeyset('instance-guid'), value: InstanceGuid}); } - if (valueIsDefined(HasUnreadableBlobs)) { + if (!isNil(HasUnreadableBlobs)) { rightColumn.push({ label: vDiskInfoKeyset('has-unreadable-blobs'), value: HasUnreadableBlobs ? vDiskInfoKeyset('yes') : vDiskInfoKeyset('no'), @@ -189,22 +202,19 @@ 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; + const {StringifiedId: id, NodeId: dNodeId} = donor; - if (!id || !dVSlotId || !dNodeId || !dPDiskId) { + if (!id || !dNodeId) { return null; } - const vDiskPath = getVDiskPagePath({ - nodeId: dNodeId, - pDiskId: dPDiskId, - vDiskSlotId: dVSlotId, - }); + const vDiskPath = getVDiskPagePath( + { + nodeId: dNodeId, + vDiskId: id, + }, + {database}, + ); return ( @@ -224,19 +234,16 @@ export function VDiskInfo({ }); } } - - const diskParamsDefined = - valueIsDefined(PDiskId) && valueIsDefined(NodeId) && valueIsDefined(VDiskSlotId); - - if (diskParamsDefined) { - const links: React.ReactNode[] = []; - + const links: React.ReactNode[] = []; + if (!isNil(StringifiedId)) { if (withVDiskPageLink) { - const vDiskPagePath = getVDiskPagePath({ - vDiskSlotId: VDiskSlotId, - pDiskId: PDiskId, - nodeId: NodeId, - }); + const vDiskPagePath = getVDiskPagePath( + { + nodeId: NodeId, + vDiskId: StringifiedId, + }, + {database}, + ); links.push( ({ />, ); } + } - if (isUserAllowedToMakeChanges) { - const vDiskInternalViewerPath = createVDiskDeveloperUILink({ - nodeId: NodeId, - pDiskId: PDiskId, - vDiskSlotId: VDiskSlotId, - }); + if (isUserAllowedToMakeChanges && !isNil(NodeId) && !isNil(VDiskSlotId) && !isNil(PDiskId)) { + const vDiskInternalViewerPath = createVDiskDeveloperUILink({ + nodeId: NodeId, + pDiskId: PDiskId, + vDiskSlotId: VDiskSlotId, + }); - links.push( - , - ); - } + links.push( + , + ); + } - if (links.length) { - rightColumn.push({ - label: vDiskInfoKeyset('links'), - value: ( - - {links} - - ), - }); - } + if (links.length) { + rightColumn.push({ + label: vDiskInfoKeyset('links'), + value: ( + + {links} + + ), + }); } const title = data && withTitle ? : null; diff --git a/src/components/VDiskInfo/i18n/en.json b/src/components/VDiskInfo/i18n/en.json index 6062b3736d..eb5dd9deab 100644 --- a/src/components/VDiskInfo/i18n/en.json +++ b/src/components/VDiskInfo/i18n/en.json @@ -1,5 +1,6 @@ { "slot-id": "VDisk Slot Id", + "label_pdisk-id": "PDisk Id", "pool-name": "Storage Pool Name", "kind": "Kind", diff --git a/src/components/VDiskPopup/VDiskPopup.tsx b/src/components/VDiskPopup/VDiskPopup.tsx index 1af84d0048..dccb4040e5 100644 --- a/src/components/VDiskPopup/VDiskPopup.tsx +++ b/src/components/VDiskPopup/VDiskPopup.tsx @@ -13,6 +13,7 @@ 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'; +import {useDatabaseFromQuery} from '../../utils/hooks/useDatabaseFromQuery'; import { useIsUserAllowedToMakeChanges, useIsViewerUser, @@ -71,7 +72,11 @@ const prepareUnavailableVDiskData = (data: UnavailableDonor, withDeveloperUILink }; // eslint-disable-next-line complexity -const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => { +const prepareVDiskData = ( + data: PreparedVDisk, + withDeveloperUILink: boolean | undefined, + query: {database: string | undefined}, +) => { const { NodeId, PDiskId, @@ -89,6 +94,7 @@ const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => ReadThroughput, WriteThroughput, StoragePoolName, + VDiskId, } = data; const vdiskData: InfoViewerItem[] = [ @@ -201,7 +207,10 @@ const prepareVDiskData = (data: PreparedVDisk, withDeveloperUILink?: boolean) => }) : undefined; - const vDiskPagePath = getVDiskLink({VDiskSlotId, PDiskId, NodeId, StringifiedId}); + const vDiskPagePath = getVDiskLink( + {VDiskSlotId, PDiskId, NodeId, StringifiedId, VDiskId}, + query, + ); if (vDiskPagePath) { vdiskData.push({ label: vDiskPopupKeyset('label_links'), @@ -238,15 +247,17 @@ export const VDiskPopup = ({data}: VDiskPopupProps) => { const isUserAllowedToMakeChanges = useIsUserAllowedToMakeChanges(); + const database = useDatabaseFromQuery(); + const vdiskInfo = React.useMemo( () => isFullData - ? prepareVDiskData(data, isUserAllowedToMakeChanges) + ? prepareVDiskData(data, isUserAllowedToMakeChanges, {database}) : prepareUnavailableVDiskData(data, isUserAllowedToMakeChanges), - [data, isFullData, isUserAllowedToMakeChanges], + [data, isFullData, isUserAllowedToMakeChanges, database], ); - const nodesMap = useTypedSelector(selectNodesMap); + const nodesMap = useTypedSelector((state) => selectNodesMap(state, database)); const nodeData = valueIsDefined(data.NodeId) ? nodesMap?.get(data.NodeId) : undefined; const pdiskInfo = React.useMemo( () => @@ -262,7 +273,11 @@ export const VDiskPopup = ({data}: VDiskPopupProps) => { for (const donor of donors) { donorsInfo.push({ label: vDiskPopupKeyset('label_vdisk'), - value: {donor.StringifiedId}, + value: ( + + {donor.StringifiedId} + + ), }); } } diff --git a/src/containers/App/Content.tsx b/src/containers/App/Content.tsx index 2fdf24edf0..026ea9508d 100644 --- a/src/containers/App/Content.tsx +++ b/src/containers/App/Content.tsx @@ -201,7 +201,8 @@ function GetUser({children}: {children: React.ReactNode}) { } function GetNodesList() { - nodesListApi.useGetNodesListQuery(undefined); + const database = useDatabaseFromQuery(); + nodesListApi.useGetNodesListQuery({database}, undefined); return null; } diff --git a/src/containers/Header/breadcrumbs.tsx b/src/containers/Header/breadcrumbs.tsx index 07be45b450..ff21cad0cf 100644 --- a/src/containers/Header/breadcrumbs.tsx +++ b/src/containers/Header/breadcrumbs.tsx @@ -6,7 +6,7 @@ import { } from '@gravity-ui/icons'; import {TabletIcon} from '../../components/TabletIcon/TabletIcon'; -import routes, {getPDiskPagePath} from '../../routes'; +import routes, {getPDiskPagePath, getStorageGroupPath} from '../../routes'; import type { BreadcrumbsOptions, ClusterBreadcrumbsOptions, @@ -38,7 +38,10 @@ export interface RawBreadcrumbItem { } interface GetBreadcrumbs { - (options: T & {singleClusterMode: boolean}, query?: U): RawBreadcrumbItem[]; + ( + options: T & {singleClusterMode: boolean; isViewerUser?: boolean}, + query?: U, + ): RawBreadcrumbItem[]; } const getQueryForTenant = (type: 'nodes' | 'tablets') => ({ @@ -133,6 +136,9 @@ function getNodeIcon(nodeRole: 'Storage' | 'Compute' | undefined) { } const getPDiskBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { + if (!options.isViewerUser) { + return []; + } const {nodeId, pDiskId, nodeRole} = options; const breadcrumbs = getNodeBreadcrumbs({ @@ -157,35 +163,38 @@ const getPDiskBreadcrumbs: GetBreadcrumbs = (options, q return breadcrumbs; }; -const getVDiskBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { - const {vDiskSlotId} = options; +const getStorageGroupBreadcrumbs: GetBreadcrumbs = ( + options, + query = {}, +) => { + const {groupId, tenantName} = options; - const breadcrumbs = getPDiskBreadcrumbs(options, query); + const breadcrumbs = tenantName + ? getTenantBreadcrumbs(options, query) + : getClusterBreadcrumbs(options, query); - let text = headerKeyset('breadcrumbs.vDisk'); - if (vDiskSlotId) { - text += ` ${vDiskSlotId}`; + let text = headerKeyset('breadcrumbs.storageGroup'); + if (groupId) { + text += ` ${groupId}`; } const lastItem = { text, + link: groupId ? getStorageGroupPath(groupId, {database: tenantName}) : undefined, }; breadcrumbs.push(lastItem); return breadcrumbs; }; -const getStorageGroupBreadcrumbs: GetBreadcrumbs = ( - options, - query = {}, -) => { - const {groupId} = options; +const getVDiskBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { + const {vDiskId} = options; - const breadcrumbs = getClusterBreadcrumbs(options, query); + const breadcrumbs = getStorageGroupBreadcrumbs(options, query); - let text = headerKeyset('breadcrumbs.storageGroup'); - if (groupId) { - text += ` ${groupId}`; + let text = headerKeyset('breadcrumbs.vDisk'); + if (vDiskId) { + text += ` ${vDiskId}`; } const lastItem = { @@ -226,7 +235,7 @@ const mapPageToGetter = { export const getBreadcrumbs = ( page: Page, - options: BreadcrumbsOptions & {singleClusterMode: boolean}, + options: BreadcrumbsOptions & {singleClusterMode: boolean; isViewerUser?: boolean}, rawBreadcrumbs: RawBreadcrumbItem[] = [], query = {}, ) => { diff --git a/src/containers/Header/i18n/en.json b/src/containers/Header/i18n/en.json index a78cae8d83..8e2a859b82 100644 --- a/src/containers/Header/i18n/en.json +++ b/src/containers/Header/i18n/en.json @@ -1,6 +1,6 @@ { "breadcrumbs.clusters": "All clusters", - "breadcrumbs.tenant": "Tenant", + "breadcrumbs.tenant": "Database", "breadcrumbs.node": "Node", "breadcrumbs.pDisk": "PDisk", "breadcrumbs.vDisk": "VDisk", diff --git a/src/containers/Heatmap/HeatmapCanvas/HeatmapCanvas.js b/src/containers/Heatmap/HeatmapCanvas/HeatmapCanvas.js index f82486192c..c955b97f49 100644 --- a/src/containers/Heatmap/HeatmapCanvas/HeatmapCanvas.js +++ b/src/containers/Heatmap/HeatmapCanvas/HeatmapCanvas.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import {getTabletPagePath} from '../../../routes'; import {basename as appBasename} from '../../../store/index'; import {cn} from '../../../utils/cn'; +import {useDatabaseFromQuery} from '../../../utils/hooks/useDatabaseFromQuery'; const b = cn('heatmap'); const defaultDimensions = {width: 0, height: 0}; @@ -18,6 +19,7 @@ export const HeatmapCanvas = (props) => { const {tablets} = props; const canvasRef = React.useRef(null); const containerRef = React.useRef(null); + const database = useDatabaseFromQuery(); function drawTablet(ctx) { return (tablet, index) => { @@ -90,7 +92,7 @@ export const HeatmapCanvas = (props) => { const generateTabletExternalLink = (tablet) => { const {TabletId: id} = tablet; const hostname = window.location.hostname; - const path = getTabletPagePath(id); + const path = getTabletPagePath(id, {database}); const protocol = 'https://'; const href = [hostname, appBasename, path] .map((item) => (item.startsWith('/') ? item.slice(1) : item)) diff --git a/src/containers/Node/Node.tsx b/src/containers/Node/Node.tsx index 3a33fa0cc9..4d7b19a535 100644 --- a/src/containers/Node/Node.tsx +++ b/src/containers/Node/Node.tsx @@ -54,7 +54,7 @@ export function Node() { const [autoRefreshInterval] = useAutoRefreshInterval(); - const params = nodeId ? {nodeId} : skipToken; + const params = nodeId ? {nodeId, database: tenantNameFromQuery?.toString()} : skipToken; const { currentData: node, isLoading, @@ -231,6 +231,7 @@ function NodePageContent({ case 'storage': { return ( { nodeId?: string | number; } -function Slot({item, pDiskId, nodeId}: SlotProps) { +function Slot({item, nodeId}: SlotProps) { const renderContent = () => { if (isVDiskSlot(item)) { - const vDiskPagePath = - valueIsDefined(item.SlotData?.VDiskSlotId) && - valueIsDefined(pDiskId) && - valueIsDefined(nodeId) - ? getVDiskPagePath({vDiskSlotId: item.SlotData.VDiskSlotId, pDiskId, nodeId}) - : undefined; + const vDiskPagePath = valueIsDefined(item.SlotData?.StringifiedId) + ? getVDiskPagePath({ + nodeId, + vDiskId: item.SlotData.StringifiedId, + }) + : undefined; return (