11import React from 'react' ;
22
33import { ArrowsOppositeToDots } from '@gravity-ui/icons' ;
4- import { Icon } from '@gravity-ui/uikit' ;
4+ import { Icon , Tab , TabList , TabProvider } from '@gravity-ui/uikit' ;
55import { skipToken } from '@reduxjs/toolkit/query' ;
66import { Helmet } from 'react-helmet-async' ;
77import { StringParam , useQueryParams } from 'use-query-params' ;
8+ import { z } from 'zod' ;
89
910import { ButtonWithConfirmDialog } from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog' ;
1011import { EntityPageTitle } from '../../components/EntityPageTitle/EntityPageTitle' ;
1112import { ResponseError } from '../../components/Errors/ResponseError' ;
1213import { InfoViewerSkeleton } from '../../components/InfoViewerSkeleton/InfoViewerSkeleton' ;
14+ import { InternalLink } from '../../components/InternalLink/InternalLink' ;
1315import { PageMetaWithAutorefresh } from '../../components/PageMeta/PageMeta' ;
1416import { VDiskInfo } from '../../components/VDiskInfo/VDiskInfo' ;
17+ import { getVDiskPagePath } from '../../routes' ;
1518import { api } from '../../store/reducers/api' ;
1619import { useDiskPagesAvailable } from '../../store/reducers/capabilities/hooks' ;
1720import { setHeaderBreadcrumbs } from '../../store/reducers/header/header' ;
@@ -25,26 +28,52 @@ import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks';
2528import { useIsUserAllowedToMakeChanges } from '../../utils/hooks/useIsUserAllowedToMakeChanges' ;
2629import { PaginatedStorage } from '../Storage/PaginatedStorage' ;
2730
31+ import { VDiskTablets } from './VDiskTablets' ;
2832import { vDiskPageKeyset } from './i18n' ;
2933
3034import './VDiskPage.scss' ;
3135
3236const vDiskPageCn = cn ( 'ydb-vdisk-page' ) ;
3337
38+ const VDISK_TABS_IDS = {
39+ storage : 'storage' ,
40+ tablets : 'tablets' ,
41+ } as const ;
42+
43+ const VDISK_PAGE_TABS = [
44+ {
45+ id : VDISK_TABS_IDS . storage ,
46+ get title ( ) {
47+ return vDiskPageKeyset ( 'storage' ) ;
48+ } ,
49+ } ,
50+ {
51+ id : VDISK_TABS_IDS . tablets ,
52+ get title ( ) {
53+ return vDiskPageKeyset ( 'tablets' ) ;
54+ } ,
55+ } ,
56+ ] ;
57+
58+ const vDiskTabSchema = z . nativeEnum ( VDISK_TABS_IDS ) . catch ( VDISK_TABS_IDS . storage ) ;
59+
3460export function VDiskPage ( ) {
3561 const dispatch = useTypedDispatch ( ) ;
3662
3763 const containerRef = React . useRef < HTMLDivElement > ( null ) ;
3864 const isUserAllowedToMakeChanges = useIsUserAllowedToMakeChanges ( ) ;
3965 const newDiskApiAvailable = useDiskPagesAvailable ( ) ;
4066
41- const [ { nodeId, pDiskId, vDiskSlotId, vDiskId : vDiskIdParam } ] = useQueryParams ( {
67+ const [ { nodeId, pDiskId, vDiskSlotId, vDiskId : vDiskIdParam , activeTab } ] = useQueryParams ( {
4268 nodeId : StringParam ,
4369 pDiskId : StringParam ,
4470 vDiskSlotId : StringParam ,
4571 vDiskId : StringParam ,
72+ activeTab : StringParam ,
4673 } ) ;
4774
75+ const vDiskTab = vDiskTabSchema . parse ( activeTab ) ;
76+
4877 React . useEffect ( ( ) => {
4978 dispatch ( setHeaderBreadcrumbs ( 'vDisk' , { nodeId, pDiskId, vDiskSlotId} ) ) ;
5079 } , [ dispatch , nodeId , pDiskId , vDiskSlotId ] ) ;
@@ -185,24 +214,67 @@ export function VDiskPage() {
185214 return < VDiskInfo data = { vDiskData } className = { vDiskPageCn ( 'info' ) } wrap /> ;
186215 } ;
187216
217+ const renderTabs = ( ) => {
218+ const vDiskParamsDefined =
219+ valueIsDefined ( nodeId ) && valueIsDefined ( pDiskId ) && valueIsDefined ( vDiskSlotId ) ;
220+
221+ return (
222+ < div className = { vDiskPageCn ( 'tabs' ) } >
223+ < TabProvider value = { vDiskTab } >
224+ < TabList size = "l" >
225+ { VDISK_PAGE_TABS . map ( ( { id, title} ) => {
226+ const path = vDiskParamsDefined
227+ ? getVDiskPagePath ( { nodeId, pDiskId, vDiskSlotId} , { activeTab : id } )
228+ : undefined ;
229+ return (
230+ < Tab key = { id } value = { id } >
231+ < InternalLink as = "tab" to = { path } >
232+ { title }
233+ </ InternalLink >
234+ </ Tab >
235+ ) ;
236+ } ) }
237+ </ TabList >
238+ </ TabProvider >
239+ </ div >
240+ ) ;
241+ } ;
242+
243+ const renderTabsContent = ( ) => {
244+ switch ( vDiskTab ) {
245+ case 'storage' : {
246+ return renderStorageInfo ( ) ;
247+ }
248+ case 'tablets' : {
249+ return (
250+ < VDiskTablets
251+ nodeId = { nodeId ?? undefined }
252+ pDiskId = { pDiskId ?? undefined }
253+ vDiskSlotId = { vDiskSlotId ?? undefined }
254+ className = { vDiskPageCn ( 'tablets-content' ) }
255+ />
256+ ) ;
257+ }
258+ default :
259+ return null ;
260+ }
261+ } ;
262+
188263 const renderStorageInfo = ( ) => {
189264 if ( valueIsDefined ( GroupID ) && valueIsDefined ( nodeId ) ) {
190265 return (
191- < React . Fragment >
192- < div className = { vDiskPageCn ( 'storage-title' ) } > { vDiskPageKeyset ( 'storage' ) } </ div >
193- < PaginatedStorage
194- groupId = { GroupID }
195- nodeId = { nodeId }
196- pDiskId = { pDiskId ?? undefined }
197- scrollContainerRef = { containerRef }
198- viewContext = { {
199- groupId : GroupID ?. toString ( ) ,
200- nodeId : nodeId ?. toString ( ) ,
201- pDiskId : pDiskId ?. toString ( ) ,
202- vDiskSlotId : vDiskSlotId ?. toString ( ) ,
203- } }
204- />
205- </ React . Fragment >
266+ < PaginatedStorage
267+ groupId = { GroupID }
268+ nodeId = { nodeId }
269+ pDiskId = { pDiskId ?? undefined }
270+ scrollContainerRef = { containerRef }
271+ viewContext = { {
272+ groupId : GroupID ?. toString ( ) ,
273+ nodeId : nodeId ?. toString ( ) ,
274+ pDiskId : pDiskId ?. toString ( ) ,
275+ vDiskSlotId : vDiskSlotId ?. toString ( ) ,
276+ } }
277+ />
206278 ) ;
207279 }
208280
@@ -218,7 +290,8 @@ export function VDiskPage() {
218290 < React . Fragment >
219291 { error ? < ResponseError error = { error } /> : null }
220292 { renderInfo ( ) }
221- { renderStorageInfo ( ) }
293+ { renderTabs ( ) }
294+ { renderTabsContent ( ) }
222295 </ React . Fragment >
223296 ) ;
224297 } ;
0 commit comments