From 9d83d9b7153879954d93710b95cdc709b45e601f Mon Sep 17 00:00:00 2001 From: mufazalov Date: Wed, 20 Mar 2024 12:42:12 +0300 Subject: [PATCH 1/4] feat: add VDisk page --- .../DiskPageTitle/DiskPageTitle.scss | 17 ++ .../DiskPageTitle/DiskPageTitle.tsx | 26 +++ .../DiskStateProgressBar.scss | 0 .../DiskStateProgressBar.tsx | 6 +- src/components/GroupInfo/GroupInfo.tsx | 47 ++++ src/components/GroupInfo/i18n/en.json | 6 + src/components/GroupInfo/i18n/index.ts | 7 + .../PDiskPopup/PDiskPopup.scss | 0 .../PDiskPopup/PDiskPopup.tsx | 16 +- src/components/PageMeta/PageMeta.scss | 9 + src/components/PageMeta/PageMeta.tsx | 18 +- src/components/Stack/Stack.scss | 2 + .../Storage => components}/VDisk/VDisk.scss | 2 +- .../Storage => components}/VDisk/VDisk.tsx | 21 +- src/components/VDisk/VDiskWithDonorsStack.tsx | 49 +++++ src/components/VDiskInfo/VDiskInfo.scss | 8 + src/components/VDiskInfo/VDiskInfo.tsx | 168 +++++++++++++++ src/components/VDiskInfo/i18n/en.json | 31 +++ src/components/VDiskInfo/i18n/index.ts | 7 + .../VDiskPopup/VDiskPopup.scss | 0 .../VDiskPopup/VDiskPopup.tsx | 30 ++- src/containers/App/Content.tsx | 35 +-- src/containers/App/appSlots.tsx | 12 +- src/containers/Header/breadcrumbs.tsx | 18 ++ src/containers/Header/i18n/en.json | 1 + src/containers/Node/NodeStructure/Pdisk.tsx | 2 +- src/containers/Node/NodeStructure/Vdisk.tsx | 117 +--------- .../{PDisk => PDiskPage}/PDiskGroups.tsx | 0 .../PDisk.scss => PDiskPage/PDiskPage.scss} | 14 -- .../PDisk.tsx => PDiskPage/PDiskPage.tsx} | 32 +-- .../{PDisk => PDiskPage}/i18n/en.json | 0 .../{PDisk => PDiskPage}/i18n/index.ts | 0 src/containers/{PDisk => PDiskPage}/shared.ts | 0 .../Storage/DiskStateProgressBar/index.ts | 1 - src/containers/Storage/PDisk/PDisk.scss | 2 - src/containers/Storage/PDisk/PDisk.tsx | 39 +--- src/containers/Storage/PDiskPopup/index.ts | 1 - src/containers/Storage/Storage.tsx | 7 +- .../Storage/StorageGroups/StorageGroups.scss | 8 +- .../StorageGroups/getStorageGroupsColumns.tsx | 33 +-- src/containers/Storage/VDisk/index.ts | 1 - src/containers/Storage/VDiskPopup/index.ts | 1 - src/containers/Storage/VirtualStorage.tsx | 13 +- src/containers/Storage/utils/types.ts | 6 - .../Diagnostics/Partitions/Partitions.tsx | 6 +- src/containers/VDiskPage/VDiskPage.scss | 32 +++ src/containers/VDiskPage/VDiskPage.tsx | 200 ++++++++++++++++++ src/containers/VDiskPage/i18n/en.json | 10 + src/containers/VDiskPage/i18n/index.ts | 7 + .../ClusterNodesMapContext.tsx | 32 +++ src/routes.ts | 11 + src/services/api.ts | 63 +++++- src/store/reducers/header/types.ts | 14 +- src/store/reducers/index.ts | 2 + src/store/reducers/node/selectors.ts | 2 + src/store/reducers/storage/types.ts | 2 + src/store/reducers/vdisk/types.ts | 34 +++ src/store/reducers/vdisk/utils.ts | 54 +++++ src/store/reducers/vdisk/vdisk.ts | 102 +++++++++ src/utils/disks/constants.ts | 8 +- src/utils/disks/types.ts | 7 +- 61 files changed, 1099 insertions(+), 300 deletions(-) create mode 100644 src/components/DiskPageTitle/DiskPageTitle.scss create mode 100644 src/components/DiskPageTitle/DiskPageTitle.tsx rename src/{containers/Storage => components}/DiskStateProgressBar/DiskStateProgressBar.scss (100%) rename src/{containers/Storage => components}/DiskStateProgressBar/DiskStateProgressBar.tsx (91%) create mode 100644 src/components/GroupInfo/GroupInfo.tsx create mode 100644 src/components/GroupInfo/i18n/en.json create mode 100644 src/components/GroupInfo/i18n/index.ts rename src/{containers/Storage => components}/PDiskPopup/PDiskPopup.scss (100%) rename src/{containers/Storage => components}/PDiskPopup/PDiskPopup.tsx (82%) rename src/{containers/Storage => components}/VDisk/VDisk.scss (87%) rename src/{containers/Storage => components}/VDisk/VDisk.tsx (76%) create mode 100644 src/components/VDisk/VDiskWithDonorsStack.tsx create mode 100644 src/components/VDiskInfo/VDiskInfo.scss create mode 100644 src/components/VDiskInfo/VDiskInfo.tsx create mode 100644 src/components/VDiskInfo/i18n/en.json create mode 100644 src/components/VDiskInfo/i18n/index.ts rename src/{containers/Storage => components}/VDiskPopup/VDiskPopup.scss (100%) rename src/{containers/Storage => components}/VDiskPopup/VDiskPopup.tsx (83%) rename src/containers/{PDisk => PDiskPage}/PDiskGroups.tsx (100%) rename src/containers/{PDisk/PDisk.scss => PDiskPage/PDiskPage.scss} (62%) rename src/containers/{PDisk/PDisk.tsx => PDiskPage/PDiskPage.tsx} (86%) rename src/containers/{PDisk => PDiskPage}/i18n/en.json (100%) rename src/containers/{PDisk => PDiskPage}/i18n/index.ts (100%) rename src/containers/{PDisk => PDiskPage}/shared.ts (100%) delete mode 100644 src/containers/Storage/DiskStateProgressBar/index.ts delete mode 100644 src/containers/Storage/PDiskPopup/index.ts delete mode 100644 src/containers/Storage/VDisk/index.ts delete mode 100644 src/containers/Storage/VDiskPopup/index.ts delete mode 100644 src/containers/Storage/utils/types.ts create mode 100644 src/containers/VDiskPage/VDiskPage.scss create mode 100644 src/containers/VDiskPage/VDiskPage.tsx create mode 100644 src/containers/VDiskPage/i18n/en.json create mode 100644 src/containers/VDiskPage/i18n/index.ts create mode 100644 src/contexts/ClusterNodesMapContext/ClusterNodesMapContext.tsx create mode 100644 src/store/reducers/vdisk/types.ts create mode 100644 src/store/reducers/vdisk/utils.ts create mode 100644 src/store/reducers/vdisk/vdisk.ts diff --git a/src/components/DiskPageTitle/DiskPageTitle.scss b/src/components/DiskPageTitle/DiskPageTitle.scss new file mode 100644 index 0000000000..2c3ed73d47 --- /dev/null +++ b/src/components/DiskPageTitle/DiskPageTitle.scss @@ -0,0 +1,17 @@ +@import '../../styles/mixins.scss'; + +.ydb-disk-page-title { + display: flex; + flex-flow: row nowrap; + align-items: baseline; + + text-wrap: nowrap; + + @include header-2-typography(); + + &__prefix { + margin-right: 6px; + + color: var(--g-color-text-secondary); + } +} diff --git a/src/components/DiskPageTitle/DiskPageTitle.tsx b/src/components/DiskPageTitle/DiskPageTitle.tsx new file mode 100644 index 0000000000..06a089e592 --- /dev/null +++ b/src/components/DiskPageTitle/DiskPageTitle.tsx @@ -0,0 +1,26 @@ +import type {ReactNode} from 'react'; + +import type {EFlag} from '../../types/api/enums'; +import {cn} from '../../utils/cn'; +import {StatusIcon} from '../StatusIcon/StatusIcon'; + +import './DiskPageTitle.scss'; + +const b = cn('ydb-disk-page-title'); + +interface DiskPageTitleProps { + entityName: ReactNode; + status: EFlag; + id: ReactNode; + className?: string; +} + +export function DiskPageTitle({entityName, status, id, className}: DiskPageTitleProps) { + return ( +
+ {entityName} + + {id} +
+ ); +} diff --git a/src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss b/src/components/DiskStateProgressBar/DiskStateProgressBar.scss similarity index 100% rename from src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss rename to src/components/DiskStateProgressBar/DiskStateProgressBar.scss diff --git a/src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx b/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx similarity index 91% rename from src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx rename to src/components/DiskStateProgressBar/DiskStateProgressBar.tsx index a259c72ccd..7e538ac37a 100644 --- a/src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +++ b/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx @@ -1,9 +1,9 @@ import React from 'react'; import cn from 'bem-cn-lite'; -import {INVERTED_DISKS_KEY} from '../../../utils/constants'; -import {useSetting} from '../../../utils/hooks'; -import {getSeverityColor} from '../../../utils/disks/helpers'; +import {INVERTED_DISKS_KEY} from '../../utils/constants'; +import {useSetting} from '../../utils/hooks'; +import {getSeverityColor} from '../../utils/disks/helpers'; import './DiskStateProgressBar.scss'; diff --git a/src/components/GroupInfo/GroupInfo.tsx b/src/components/GroupInfo/GroupInfo.tsx new file mode 100644 index 0000000000..48d4ce4f57 --- /dev/null +++ b/src/components/GroupInfo/GroupInfo.tsx @@ -0,0 +1,47 @@ +import type {PreparedStorageGroup} from '../../store/reducers/storage/types'; +import {valueIsDefined} from '../../utils'; +import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters'; + +import type {InfoViewerProps} from '../InfoViewer/InfoViewer'; +import {InfoViewer} from '../InfoViewer'; +import {ProgressViewer} from '../ProgressViewer/ProgressViewer'; +import {groupInfoKeyset} from './i18n'; + +interface GroupInfoProps> + extends Omit { + data: T; +} + +export function GroupInfo>({ + data, + ...infoViewerProps +}: GroupInfoProps) { + const {GroupID, PoolName, Used, Limit, ErasureSpecies} = data; + + const groupInfo = []; + + if (valueIsDefined(GroupID)) { + groupInfo.push({label: groupInfoKeyset('group-id'), value: GroupID}); + } + if (valueIsDefined(PoolName)) { + groupInfo.push({label: groupInfoKeyset('pool-name'), value: PoolName}); + } + if (valueIsDefined(ErasureSpecies)) { + groupInfo.push({label: groupInfoKeyset('erasure'), value: ErasureSpecies}); + } + if (Number(Used) >= 0 && Number(Limit) >= 0) { + groupInfo.push({ + label: groupInfoKeyset('size'), + value: ( + + ), + }); + } + + return ; +} diff --git a/src/components/GroupInfo/i18n/en.json b/src/components/GroupInfo/i18n/en.json new file mode 100644 index 0000000000..bdff1f211c --- /dev/null +++ b/src/components/GroupInfo/i18n/en.json @@ -0,0 +1,6 @@ +{ + "group-id": "Group ID", + "pool-name": "Storage Pool Name", + "size": "Size", + "erasure": "Erasure" +} diff --git a/src/components/GroupInfo/i18n/index.ts b/src/components/GroupInfo/i18n/index.ts new file mode 100644 index 0000000000..5291c1a4a7 --- /dev/null +++ b/src/components/GroupInfo/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-group-info'; + +export const groupInfoKeyset = registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Storage/PDiskPopup/PDiskPopup.scss b/src/components/PDiskPopup/PDiskPopup.scss similarity index 100% rename from src/containers/Storage/PDiskPopup/PDiskPopup.scss rename to src/components/PDiskPopup/PDiskPopup.scss diff --git a/src/containers/Storage/PDiskPopup/PDiskPopup.tsx b/src/components/PDiskPopup/PDiskPopup.tsx similarity index 82% rename from src/containers/Storage/PDiskPopup/PDiskPopup.tsx rename to src/components/PDiskPopup/PDiskPopup.tsx index 42bc79e970..a839b184a4 100644 --- a/src/containers/Storage/PDiskPopup/PDiskPopup.tsx +++ b/src/components/PDiskPopup/PDiskPopup.tsx @@ -3,14 +3,14 @@ import cn from 'bem-cn-lite'; import {Popup, PopupProps} from '@gravity-ui/uikit'; -import type {NodesMap} from '../../../types/store/nodesList'; +import type {NodesMap} from '../../types/store/nodesList'; +import {EFlag} from '../../types/api/enums'; +import type {PreparedPDisk} from '../../utils/disks/types'; +import {getPDiskId} from '../../utils/dataFormatters/dataFormatters'; +import {bytesToGB} from '../../utils/utils'; +import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; -import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer'; - -import {EFlag} from '../../../types/api/enums'; -import type {PreparedPDisk} from '../../../utils/disks/types'; -import {getPDiskId} from '../../../utils/dataFormatters/dataFormatters'; -import {bytesToGB} from '../../../utils/utils'; +import {InfoViewer, InfoViewerItem} from '../InfoViewer'; import './PDiskPopup.scss'; @@ -22,7 +22,7 @@ export const preparePDiskData = (data: PreparedPDisk, nodes?: NodesMap) => { const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Type, Device} = data; const pdiskData: InfoViewerItem[] = [ - {label: 'PDisk', value: getPDiskId({NodeId, PDiskId}) || '-'}, + {label: 'PDisk', value: getPDiskId({NodeId, PDiskId}) || EMPTY_DATA_PLACEHOLDER}, {label: 'State', value: State || 'not available'}, {label: 'Type', value: Type || 'unknown'}, ]; diff --git a/src/components/PageMeta/PageMeta.scss b/src/components/PageMeta/PageMeta.scss index 7cffe23e7f..1f5c2c9fda 100644 --- a/src/components/PageMeta/PageMeta.scss +++ b/src/components/PageMeta/PageMeta.scss @@ -4,7 +4,16 @@ display: flex; flex-flow: row nowrap; + height: var(--g-text-body-2-line-height); + + text-wrap: nowrap; + color: var(--g-color-text-primary); @include body-2-typography(); + + &__skeleton { + width: 80%; + height: 80%; + } } diff --git a/src/components/PageMeta/PageMeta.tsx b/src/components/PageMeta/PageMeta.tsx index 17f9e03c44..7017d35901 100644 --- a/src/components/PageMeta/PageMeta.tsx +++ b/src/components/PageMeta/PageMeta.tsx @@ -1,3 +1,4 @@ +import {Skeleton} from '@gravity-ui/uikit'; import {cn} from '../../utils/cn'; import './PageMeta.scss'; @@ -6,12 +7,17 @@ const b = cn('ydb-page-meta'); interface PageMetaProps { items: (string | undefined)[]; className?: string; + loading?: boolean; } -export function PageMeta({items, className}: PageMetaProps) { - return ( -
- {items.filter((item) => Boolean(item)).join('\u00a0\u00a0\u00B7\u00a0\u00a0')} -
- ); +export function PageMeta({items, loading, className}: PageMetaProps) { + const renderContent = () => { + if (loading) { + return ; + } + + return items.filter((item) => Boolean(item)).join('\u00a0\u00a0\u00B7\u00a0\u00a0'); + }; + + return
{renderContent()}
; } diff --git a/src/components/Stack/Stack.scss b/src/components/Stack/Stack.scss index 005dfb78ac..bdd1dc8947 100644 --- a/src/components/Stack/Stack.scss +++ b/src/components/Stack/Stack.scss @@ -8,6 +8,8 @@ position: relative; &__layer { + background: var(--g-color-base-background); + transition: transform 0.1s ease-out; &:first-child { diff --git a/src/containers/Storage/VDisk/VDisk.scss b/src/components/VDisk/VDisk.scss similarity index 87% rename from src/containers/Storage/VDisk/VDisk.scss rename to src/components/VDisk/VDisk.scss index b39bdfee8a..aab1c4e3ef 100644 --- a/src/containers/Storage/VDisk/VDisk.scss +++ b/src/components/VDisk/VDisk.scss @@ -1,4 +1,4 @@ -.vdisk-storage { +.ydb-vdisk-component { border-radius: 4px; // to match interactive area with disk shape &__content { diff --git a/src/containers/Storage/VDisk/VDisk.tsx b/src/components/VDisk/VDisk.tsx similarity index 76% rename from src/containers/Storage/VDisk/VDisk.tsx rename to src/components/VDisk/VDisk.tsx index 9e2baf4cd1..7ef45f9c3f 100644 --- a/src/containers/Storage/VDisk/VDisk.tsx +++ b/src/components/VDisk/VDisk.tsx @@ -1,23 +1,22 @@ import React, {useState, useRef} from 'react'; import cn from 'bem-cn-lite'; -import type {NodesMap} from '../../../types/store/nodesList'; +import type {NodesMap} from '../../types/store/nodesList'; -import {InternalLink} from '../../../components/InternalLink'; +import type {PreparedVDisk} from '../../utils/disks/types'; +import routes, {createHref} from '../../routes'; +import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters'; +import {isFullVDiskData} from '../../utils/disks/helpers'; -import type {PreparedVDisk} from '../../../utils/disks/types'; -import routes, {createHref} from '../../../routes'; -import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters'; -import {isFullVDiskData} from '../../../utils/disks/helpers'; +import {STRUCTURE} from '../../containers/Node/NodePages'; -import {STRUCTURE} from '../../Node/NodePages'; - -import {DiskStateProgressBar} from '../DiskStateProgressBar'; -import {VDiskPopup} from '../VDiskPopup'; +import {VDiskPopup} from '../VDiskPopup/VDiskPopup'; +import {DiskStateProgressBar} from '../DiskStateProgressBar/DiskStateProgressBar'; +import {InternalLink} from '../InternalLink'; import './VDisk.scss'; -const b = cn('vdisk-storage'); +const b = cn('ydb-vdisk-component'); interface VDiskProps { data?: PreparedVDisk; diff --git a/src/components/VDisk/VDiskWithDonorsStack.tsx b/src/components/VDisk/VDiskWithDonorsStack.tsx new file mode 100644 index 0000000000..221ccea31c --- /dev/null +++ b/src/components/VDisk/VDiskWithDonorsStack.tsx @@ -0,0 +1,49 @@ +import type {NodesMap} from '../../types/store/nodesList'; +import type {PreparedVDisk} from '../../utils/disks/types'; +import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters'; +import {isFullVDiskData} from '../../utils/disks/helpers'; + +import {Stack} from '../Stack/Stack'; +import {VDisk} from './VDisk'; + +interface VDiskWithDonorsStackProps { + data?: PreparedVDisk; + nodes?: NodesMap; + compact?: boolean; + className?: string; + stackClassName?: string; +} + +export function VDiskWithDonorsStack({ + data, + nodes, + compact, + className, + stackClassName, +}: VDiskWithDonorsStackProps) { + const donors = data?.Donors; + + const content = + donors && donors.length > 0 ? ( + + + {donors.map((donor) => { + const isFullData = isFullVDiskData(donor); + + // donor and acceptor are always in the same group + return ( + + ); + })} + + ) : ( + + ); + + return
{content}
; +} diff --git a/src/components/VDiskInfo/VDiskInfo.scss b/src/components/VDiskInfo/VDiskInfo.scss new file mode 100644 index 0000000000..244b93dccc --- /dev/null +++ b/src/components/VDiskInfo/VDiskInfo.scss @@ -0,0 +1,8 @@ +.ydb-vdisk-info { + &__links { + display: flex; + flex-flow: row wrap; + + gap: 12px; + } +} diff --git a/src/components/VDiskInfo/VDiskInfo.tsx b/src/components/VDiskInfo/VDiskInfo.tsx new file mode 100644 index 0000000000..92f2873f99 --- /dev/null +++ b/src/components/VDiskInfo/VDiskInfo.tsx @@ -0,0 +1,168 @@ +import type {PreparedVDisk} from '../../utils/disks/types'; +import {valueIsDefined} from '../../utils'; +import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters'; +import {bytesToSpeed} from '../../utils/utils'; +import {cn} from '../../utils/cn'; +import {createVDiskDeveloperUILink} from '../../utils/developerUI/developerUI'; +import {getVDiskPagePath} from '../../routes'; + +import type {InfoViewerProps} from '../InfoViewer/InfoViewer'; +import {InfoViewer} from '../InfoViewer'; +import {EntityStatus} from '../EntityStatus/EntityStatus'; +import {LinkWithIcon} from '../LinkWithIcon/LinkWithIcon'; +import {ProgressViewer} from '../ProgressViewer/ProgressViewer'; +import {vDiskInfoKeyset} from './i18n'; + +import './VDiskInfo.scss'; + +const b = cn('ydb-vdisk-info'); + +interface VDiskInfoProps extends Omit { + data: T; + isVDiskPage?: boolean; +} + +// eslint-disable-next-line complexity +export function VDiskInfo({ + data, + isVDiskPage = false, + ...infoViewerProps +}: VDiskInfoProps) { + const { + AllocatedSize, + DiskSpace, + FrontQueues, + Guid, + Replicated, + VDiskState, + VDiskSlotId, + Kind, + SatisfactionRank, + AvailableSize, + HasUnreadableBlobs, + IncarnationGuid, + InstanceGuid, + StoragePoolName, + ReadThroughput, + WriteThroughput, + PDiskId, + NodeId, + } = data; + + const vdiskInfo = []; + + if (valueIsDefined(VDiskSlotId)) { + vdiskInfo.push({label: vDiskInfoKeyset('slot-id'), value: VDiskSlotId}); + } + if (valueIsDefined(StoragePoolName)) { + vdiskInfo.push({label: vDiskInfoKeyset('pool-name'), value: StoragePoolName}); + } + if (valueIsDefined(VDiskState)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('state-status'), + value: VDiskState, + }); + } + if (Number(AllocatedSize) >= 0 && Number(AvailableSize) >= 0) { + vdiskInfo.push({ + label: vDiskInfoKeyset('size'), + value: ( + + ), + }); + } + if (valueIsDefined(Kind)) { + vdiskInfo.push({label: vDiskInfoKeyset('kind'), value: Kind}); + } + if (valueIsDefined(Guid)) { + vdiskInfo.push({label: vDiskInfoKeyset('guid'), value: Guid}); + } + if (valueIsDefined(IncarnationGuid)) { + vdiskInfo.push({label: vDiskInfoKeyset('incarnation-guid'), value: IncarnationGuid}); + } + if (valueIsDefined(InstanceGuid)) { + vdiskInfo.push({label: vDiskInfoKeyset('instance-guid'), value: InstanceGuid}); + } + if (valueIsDefined(Replicated)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('replication-status'), + value: Replicated ? vDiskInfoKeyset('yes') : vDiskInfoKeyset('no'), + }); + } + if (valueIsDefined(DiskSpace)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('space-status'), + value: , + }); + } + if (valueIsDefined(SatisfactionRank?.FreshRank?.Flag)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('fresh-rank-satisfaction'), + value: , + }); + } + if (valueIsDefined(SatisfactionRank?.LevelRank?.Flag)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('level-rank-satisfaction'), + value: , + }); + } + if (valueIsDefined(FrontQueues)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('front-queues'), + value: , + }); + } + if (valueIsDefined(HasUnreadableBlobs)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('has-unreadable-blobs'), + value: HasUnreadableBlobs ? vDiskInfoKeyset('yes') : vDiskInfoKeyset('no'), + }); + } + if (valueIsDefined(ReadThroughput)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('read-throughput'), + value: bytesToSpeed(ReadThroughput), + }); + } + if (valueIsDefined(WriteThroughput)) { + vdiskInfo.push({ + label: vDiskInfoKeyset('write-throughput'), + value: bytesToSpeed(WriteThroughput), + }); + } + if (valueIsDefined(PDiskId) && valueIsDefined(NodeId) && valueIsDefined(VDiskSlotId)) { + const vDiskPagePath = getVDiskPagePath(VDiskSlotId, PDiskId, NodeId); + const vDiskInternalViewerPath = createVDiskDeveloperUILink({ + nodeId: NodeId, + pDiskId: PDiskId, + vDiskSlotId: VDiskSlotId, + }); + + vdiskInfo.push({ + label: vDiskInfoKeyset('links'), + value: ( + + {!isVDiskPage && ( + + )} + + + ), + }); + } + + return ; +} diff --git a/src/components/VDiskInfo/i18n/en.json b/src/components/VDiskInfo/i18n/en.json new file mode 100644 index 0000000000..de09356405 --- /dev/null +++ b/src/components/VDiskInfo/i18n/en.json @@ -0,0 +1,31 @@ +{ + "slot-id": "VDisk Slot Id", + "pool-name": "Storage Pool Name", + "kind": "Kind", + + "guid": "GUID", + "incarnation-guid": "Incarnation GUID", + "instance-guid": "Instance GUID", + + "replication-status": "Replicated", + "state-status": "VDisk State", + "space-status": "Disk Space", + + "fresh-rank-satisfaction": "Fresh Rank Satisfaction", + "level-rank-satisfaction": "Level Rank Satisfaction", + + "front-queues": "Front Queues", + "has-unreadable-blobs": "Has Unreadable Blobs", + + "size": "Size", + + "read-throughput": "Read Throughput", + "write-throughput": "Write Throughput", + + "links": "Links", + "vdisk-page": "VDisk Page", + "developer-ui": "Developer UI", + + "yes": "Yes", + "no": "No" +} diff --git a/src/components/VDiskInfo/i18n/index.ts b/src/components/VDiskInfo/i18n/index.ts new file mode 100644 index 0000000000..8ae3483f69 --- /dev/null +++ b/src/components/VDiskInfo/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-vDisk-info'; + +export const vDiskInfoKeyset = registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Storage/VDiskPopup/VDiskPopup.scss b/src/components/VDiskPopup/VDiskPopup.scss similarity index 100% rename from src/containers/Storage/VDiskPopup/VDiskPopup.scss rename to src/components/VDiskPopup/VDiskPopup.scss diff --git a/src/containers/Storage/VDiskPopup/VDiskPopup.tsx b/src/components/VDiskPopup/VDiskPopup.tsx similarity index 83% rename from src/containers/Storage/VDiskPopup/VDiskPopup.tsx rename to src/components/VDiskPopup/VDiskPopup.tsx index 81f391d415..5238b1acbd 100644 --- a/src/containers/Storage/VDiskPopup/VDiskPopup.tsx +++ b/src/components/VDiskPopup/VDiskPopup.tsx @@ -3,19 +3,17 @@ import cn from 'bem-cn-lite'; import {Label, Popup, PopupProps} from '@gravity-ui/uikit'; -import type {NodesMap} from '../../../types/store/nodesList'; - -import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer'; - -import {EFlag} from '../../../types/api/enums'; -import type {TVDiskStateInfo} from '../../../types/api/vdisk'; -import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters'; -import {bytesToGB, bytesToSpeed} from '../../../utils/utils'; -import {isFullVDiskData} from '../../../utils/disks/helpers'; - -import type {UnavailableDonor} from '../utils/types'; - -import {preparePDiskData} from '../PDiskPopup'; +import type {NodesMap} from '../../types/store/nodesList'; +import {EFlag} from '../../types/api/enums'; +import type {TVDiskStateInfo} from '../../types/api/vdisk'; +import type {UnavailableDonor} from '../../utils/disks/types'; +import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters'; +import {bytesToGB, bytesToSpeed} from '../../utils/utils'; +import {isFullVDiskData} from '../../utils/disks/helpers'; +import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; + +import {preparePDiskData} from '../PDiskPopup/PDiskPopup'; +import {InfoViewer, InfoViewerItem} from '../InfoViewer'; import './VDiskPopup.scss'; @@ -31,9 +29,9 @@ const prepareUnavailableVDiskData = (data: UnavailableDonor) => { } vdiskData.push( - {label: 'NodeId', value: NodeId ?? '–'}, - {label: 'PDiskId', value: PDiskId ?? '–'}, - {label: 'VSlotId', value: VSlotId ?? '–'}, + {label: 'NodeId', value: NodeId ?? EMPTY_DATA_PLACEHOLDER}, + {label: 'PDiskId', value: PDiskId ?? EMPTY_DATA_PLACEHOLDER}, + {label: 'VSlotId', value: VSlotId ?? EMPTY_DATA_PLACEHOLDER}, ); return vdiskData; diff --git a/src/containers/App/Content.tsx b/src/containers/App/Content.tsx index 1f85037cf2..298eb36938 100644 --- a/src/containers/App/Content.tsx +++ b/src/containers/App/Content.tsx @@ -9,12 +9,15 @@ import {Clusters} from '../Clusters/Clusters'; import Cluster from '../Cluster/Cluster'; import Tenant from '../Tenant/Tenant'; import Node from '../Node/Node'; -import {PDisk} from '../PDisk/PDisk'; +import {PDiskPage} from '../PDiskPage/PDiskPage'; +import {VDiskPage} from '../VDiskPage/VDiskPage'; import {Tablet} from '../Tablet'; import TabletsFilters from '../TabletsFilters/TabletsFilters'; import Header from '../Header/Header'; import Authentication from '../Authentication/Authentication'; +import {ClusterNodesMapContextProvider} from '../../contexts/ClusterNodesMapContext/ClusterNodesMapContext'; + import {getUser} from '../../store/reducers/authentication/authentication'; import {getClusterPath} from '../Cluster/utils'; import {useSlots} from '../../components/slots'; @@ -23,7 +26,8 @@ import { ClusterSlot, ClustersSlot, NodeSlot, - PDiskSlot, + PDiskPageSlot, + VDiskPageSlot, RedirectSlot, RoutesSlot, TabletSlot, @@ -65,8 +69,13 @@ const routesSlots: RouteSlot[] = [ }, { path: routes.pDisk, - slot: PDiskSlot, - component: PDisk, + slot: PDiskPageSlot, + component: PDiskPage, + }, + { + path: routes.vDisk, + slot: VDiskPageSlot, + component: VDiskPage, }, { path: routes.tablet, @@ -136,14 +145,16 @@ export function Content(props: ContentProps) { {additionalRoutes?.rendered} {/* Single cluster routes */} - -
- - {routesSlots.map((route) => { - return renderRouteSlot(slots, route); - })} - - + + +
+ + {routesSlots.map((route) => { + return renderRouteSlot(slots, route); + })} + + + ); diff --git a/src/containers/App/appSlots.tsx b/src/containers/App/appSlots.tsx index 68f2955bbd..6c591f22ee 100644 --- a/src/containers/App/appSlots.tsx +++ b/src/containers/App/appSlots.tsx @@ -4,7 +4,8 @@ import type {RedirectProps, RouteComponentProps} from 'react-router'; import type Cluster from '../Cluster/Cluster'; import type {Clusters} from '../Clusters/Clusters'; import type Node from '../Node/Node'; -import type {PDisk} from '../PDisk/PDisk'; +import type {PDiskPage} from '../PDiskPage/PDiskPage'; +import type {VDiskPage} from '../VDiskPage/VDiskPage'; import type {Tablet} from '../Tablet'; import type TabletsFilters from '../TabletsFilters/TabletsFilters'; import type Tenant from '../Tenant/Tenant'; @@ -29,11 +30,16 @@ export const NodeSlot = createSlot<{ | React.ReactNode | ((props: {component: typeof Node} & RouteComponentProps) => React.ReactNode); }>('node'); -export const PDiskSlot = createSlot<{ +export const PDiskPageSlot = createSlot<{ children: | React.ReactNode - | ((props: {component: typeof PDisk} & RouteComponentProps) => React.ReactNode); + | ((props: {component: typeof PDiskPage} & RouteComponentProps) => React.ReactNode); }>('pDisk'); +export const VDiskPageSlot = createSlot<{ + children: + | React.ReactNode + | ((props: {component: typeof VDiskPage} & RouteComponentProps) => React.ReactNode); +}>('vDisk'); export const TabletSlot = createSlot<{ children: | React.ReactNode diff --git a/src/containers/Header/breadcrumbs.tsx b/src/containers/Header/breadcrumbs.tsx index 566c3e3390..18eb31744a 100644 --- a/src/containers/Header/breadcrumbs.tsx +++ b/src/containers/Header/breadcrumbs.tsx @@ -14,6 +14,7 @@ import type { TabletBreadcrumbsOptions, TabletsBreadcrumbsOptions, TenantBreadcrumbsOptions, + VDiskBreadcrumbsOptions, } from '../../store/reducers/header/types'; import { TENANT_DIAGNOSTICS_TABS_IDS, @@ -123,6 +124,20 @@ const getPDiskBreadcrumbs = (options: PDiskBreadcrumbsOptions, query = {}) => { return breadcrumbs; }; +const getVDiskBreadcrumbs = (options: VDiskBreadcrumbsOptions, query = {}) => { + const {vDiskSlotId} = options; + + const breadcrumbs = getPDiskBreadcrumbs(options, query); + + const text = vDiskSlotId + ? `${headerKeyset('breadcrumbs.vDisk')} ${vDiskSlotId}` + : headerKeyset('breadcrumbs.vDisk'); + + breadcrumbs.push({text}); + + return breadcrumbs; +}; + const getTabletsBreadcrubms = ( options: TabletsBreadcrumbsOptions, query = {}, @@ -190,6 +205,9 @@ export const getBreadcrumbs = ( case 'pDisk': { return [...rawBreadcrumbs, ...getPDiskBreadcrumbs(options, query)]; } + case 'vDisk': { + return [...rawBreadcrumbs, ...getVDiskBreadcrumbs(options, query)]; + } case 'tablets': { return [...rawBreadcrumbs, ...getTabletsBreadcrubms(options, query)]; } diff --git a/src/containers/Header/i18n/en.json b/src/containers/Header/i18n/en.json index 2715bd53fe..764477f692 100644 --- a/src/containers/Header/i18n/en.json +++ b/src/containers/Header/i18n/en.json @@ -2,6 +2,7 @@ "breadcrumbs.tenant": "Tenant", "breadcrumbs.node": "Node", "breadcrumbs.pDisk": "PDisk", + "breadcrumbs.vDisk": "VDisk", "breadcrumbs.tablet": "Tablet", "breadcrumbs.tablets": "Tablets" } diff --git a/src/containers/Node/NodeStructure/Pdisk.tsx b/src/containers/Node/NodeStructure/Pdisk.tsx index 534d1a4a7b..655e4af8d1 100644 --- a/src/containers/Node/NodeStructure/Pdisk.tsx +++ b/src/containers/Node/NodeStructure/Pdisk.tsx @@ -140,7 +140,7 @@ function getColumns({ return ( } + content={} tooltipContentClassName={b('vdisk-details')} >