From 8e05166388964c9fbe01984723c8d3e4cedd48a8 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Tue, 17 Sep 2024 20:36:45 +0300 Subject: [PATCH] feat: add storage group page (#1289) --- src/components/PDiskInfo/PDiskInfo.scss | 14 -- src/components/PDiskInfo/PDiskInfo.tsx | 14 +- .../StorageGroupInfo/StorageGroupInfo.tsx | 168 +++++++++++++++ src/components/StorageGroupInfo/i18n/en.json | 22 ++ src/components/StorageGroupInfo/i18n/index.ts | 7 + src/containers/App/Content.tsx | 10 + src/containers/App/appSlots.tsx | 6 + src/containers/Header/breadcrumbs.tsx | 23 ++ src/containers/Header/i18n/en.json | 3 +- src/containers/PDiskPage/PDiskPage.scss | 19 +- src/containers/PDiskPage/PDiskPage.tsx | 18 +- .../columns/getStorageGroupsColumns.tsx | 10 +- .../StorageGroupPage/StorageGroupPage.scss | 40 ++++ .../StorageGroupPage/StorageGroupPage.tsx | 203 ++++++++++++++++++ src/containers/StorageGroupPage/i18n/en.json | 6 + src/containers/StorageGroupPage/i18n/index.ts | 7 + src/routes.ts | 6 + src/store/reducers/header/types.ts | 17 +- src/store/reducers/nodes/types.ts | 1 + src/store/reducers/storage/types.ts | 21 ++ src/store/reducers/storage/utils.ts | 10 +- 21 files changed, 587 insertions(+), 38 deletions(-) create mode 100644 src/components/StorageGroupInfo/StorageGroupInfo.tsx create mode 100644 src/components/StorageGroupInfo/i18n/en.json create mode 100644 src/components/StorageGroupInfo/i18n/index.ts create mode 100644 src/containers/StorageGroupPage/StorageGroupPage.scss create mode 100644 src/containers/StorageGroupPage/StorageGroupPage.tsx create mode 100644 src/containers/StorageGroupPage/i18n/en.json create mode 100644 src/containers/StorageGroupPage/i18n/index.ts diff --git a/src/components/PDiskInfo/PDiskInfo.scss b/src/components/PDiskInfo/PDiskInfo.scss index a9875fd9e..db8ae2f3f 100644 --- a/src/components/PDiskInfo/PDiskInfo.scss +++ b/src/components/PDiskInfo/PDiskInfo.scss @@ -1,18 +1,4 @@ .ydb-pdisk-info { - &__wrapper { - display: flex; - flex-flow: row wrap; - gap: 7px; - } - - &__col { - display: flex; - flex-direction: column; - gap: 7px; - - width: 500px; - } - &__links { display: flex; flex-flow: row wrap; diff --git a/src/components/PDiskInfo/PDiskInfo.tsx b/src/components/PDiskInfo/PDiskInfo.tsx index 85aa88fb9..995ea2b20 100644 --- a/src/components/PDiskInfo/PDiskInfo.tsx +++ b/src/components/PDiskInfo/PDiskInfo.tsx @@ -1,3 +1,5 @@ +import {Flex} from '@gravity-ui/uikit'; + import {getPDiskPagePath} from '../../routes'; import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication'; import {useDiskPagesAvailable} from '../../store/reducers/capabilities/hooks'; @@ -199,15 +201,15 @@ export function PDiskInfo({ }); return ( -
-
+ + null} /> null} /> -
-
+ + null} /> null} /> -
-
+ + ); } diff --git a/src/components/StorageGroupInfo/StorageGroupInfo.tsx b/src/components/StorageGroupInfo/StorageGroupInfo.tsx new file mode 100644 index 000000000..2db2eab63 --- /dev/null +++ b/src/components/StorageGroupInfo/StorageGroupInfo.tsx @@ -0,0 +1,168 @@ +import {Flex} from '@gravity-ui/uikit'; + +import type {PreparedStorageGroup} from '../../store/reducers/storage/types'; +import {valueIsDefined} from '../../utils'; +import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters'; +import {formatToMs} from '../../utils/timeParsers'; +import {bytesToSpeed} from '../../utils/utils'; +import {EntityStatus} from '../EntityStatus/EntityStatus'; +import {InfoViewer} from '../InfoViewer'; +import type {InfoViewerProps} from '../InfoViewer/InfoViewer'; +import {ProgressViewer} from '../ProgressViewer/ProgressViewer'; + +import {storageGroupInfoKeyset} from './i18n'; + +interface StorageGroupInfoProps extends Omit { + data?: PreparedStorageGroup; + className?: string; +} + +// eslint-disable-next-line complexity +export function StorageGroupInfo({data, className, ...infoViewerProps}: StorageGroupInfoProps) { + const { + Encryption, + Overall, + DiskSpace, + MediaType, + ErasureSpecies, + Used, + Limit, + Usage, + Read, + Write, + GroupGeneration, + Latency, + AllocationUnits, + State, + MissingDisks, + Available, + LatencyPutTabletLog, + LatencyPutUserData, + LatencyGetFast, + } = data || {}; + + const storageGroupInfoFirstColumn = []; + + if (valueIsDefined(GroupGeneration)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('group-generation'), + value: GroupGeneration, + }); + } + if (valueIsDefined(ErasureSpecies)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('erasure-species'), + value: ErasureSpecies, + }); + } + if (valueIsDefined(MediaType)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('media-type'), + value: MediaType, + }); + } + if (valueIsDefined(Encryption)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('encryption'), + value: Encryption ? storageGroupInfoKeyset('yes') : storageGroupInfoKeyset('no'), + }); + } + if (valueIsDefined(Overall)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('overall'), + value: , + }); + } + if (valueIsDefined(State)) { + storageGroupInfoFirstColumn.push({label: storageGroupInfoKeyset('state'), value: State}); + } + if (valueIsDefined(MissingDisks)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('missing-disks'), + value: MissingDisks, + }); + } + + const storageGroupInfoSecondColumn = []; + + if (valueIsDefined(Used) && valueIsDefined(Limit)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('used-space'), + value: ( + + ), + }); + } + if (valueIsDefined(Available)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('available'), + value: formatStorageValuesToGb(Number(Available)), + }); + } + if (valueIsDefined(Usage)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('usage'), + value: `${Usage.toFixed(2)}%`, + }); + } + if (valueIsDefined(DiskSpace)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('disk-space'), + value: , + }); + } + if (valueIsDefined(Latency)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('latency'), + value: , + }); + } + if (valueIsDefined(LatencyPutTabletLog)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('latency-put-tablet-log'), + value: formatToMs(LatencyPutTabletLog), + }); + } + if (valueIsDefined(LatencyPutUserData)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('latency-put-user-data'), + value: formatToMs(LatencyPutUserData), + }); + } + if (valueIsDefined(LatencyGetFast)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('latency-get-fast'), + value: formatToMs(LatencyGetFast), + }); + } + if (valueIsDefined(AllocationUnits)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('allocation-units'), + value: AllocationUnits, + }); + } + if (valueIsDefined(Read)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('read-throughput'), + value: bytesToSpeed(Number(Read)), + }); + } + if (valueIsDefined(Write)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('write-throughput'), + value: bytesToSpeed(Number(Write)), + }); + } + + return ( + + + + + ); +} diff --git a/src/components/StorageGroupInfo/i18n/en.json b/src/components/StorageGroupInfo/i18n/en.json new file mode 100644 index 000000000..8357006f3 --- /dev/null +++ b/src/components/StorageGroupInfo/i18n/en.json @@ -0,0 +1,22 @@ +{ + "encryption": "Encryption", + "overall": "Overall", + "disk-space": "Disk Space", + "media-type": "Media Type", + "erasure-species": "Erasure Species", + "used-space": "Used Space", + "usage": "Usage", + "read-throughput": "Read Throughput", + "write-throughput": "Write Throughput", + "yes": "Yes", + "no": "No", + "group-generation": "Group Generation", + "latency": "Latency", + "allocation-units": "Units", + "state": "State", + "missing-disks": "Missing Disks", + "available": "Available Space", + "latency-put-tablet-log": "Latency (Put Tablet Log)", + "latency-put-user-data": "Latency (Put User Data)", + "latency-get-fast": "Latency (Get Fast)" +} diff --git a/src/components/StorageGroupInfo/i18n/index.ts b/src/components/StorageGroupInfo/i18n/index.ts new file mode 100644 index 000000000..f006a4eaa --- /dev/null +++ b/src/components/StorageGroupInfo/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'storage-group-info'; + +export const storageGroupInfoKeyset = registerKeysets(COMPONENT, {en}); diff --git a/src/containers/App/Content.tsx b/src/containers/App/Content.tsx index ae7291ca4..9f7ceab28 100644 --- a/src/containers/App/Content.tsx +++ b/src/containers/App/Content.tsx @@ -28,6 +28,7 @@ import { PDiskPageSlot, RedirectSlot, RoutesSlot, + StorageGroupSlot, TabletSlot, TenantSlot, VDiskPageSlot, @@ -76,6 +77,15 @@ const routesSlots: RouteSlot[] = [ component: lazyComponent(() => import('../VDiskPage/VDiskPage'), 'VDiskPage'), wrapper: DataWrapper, }, + { + path: routes.storageGroup, + slot: StorageGroupSlot, + component: lazyComponent( + () => import('../StorageGroupPage/StorageGroupPage'), + 'StorageGroupPage', + ), + wrapper: DataWrapper, + }, { path: routes.tablet, slot: TabletSlot, diff --git a/src/containers/App/appSlots.tsx b/src/containers/App/appSlots.tsx index 2eb9ae1ce..aedd4b548 100644 --- a/src/containers/App/appSlots.tsx +++ b/src/containers/App/appSlots.tsx @@ -5,6 +5,7 @@ import type {Cluster} from '../Cluster/Cluster'; import type {Clusters} from '../Clusters/Clusters'; import type {Node} from '../Node/Node'; import type {PDiskPage} from '../PDiskPage/PDiskPage'; +import type {StorageGroupPage} from '../StorageGroupPage/StorageGroupPage'; import type {Tablet} from '../Tablet'; import type {Tenant} from '../Tenant/Tenant'; import type {VDiskPage} from '../VDiskPage/VDiskPage'; @@ -39,6 +40,11 @@ export const VDiskPageSlot = createSlot<{ | React.ReactNode | ((props: {component: typeof VDiskPage} & RouteComponentProps) => React.ReactNode); }>('vDisk'); +export const StorageGroupSlot = createSlot<{ + children: + | React.ReactNode + | ((props: {component: typeof StorageGroupPage} & RouteComponentProps) => React.ReactNode); +}>('storageGroup'); export const TabletSlot = createSlot<{ children: | React.ReactNode diff --git a/src/containers/Header/breadcrumbs.tsx b/src/containers/Header/breadcrumbs.tsx index ce1f2b3f7..cfc09f577 100644 --- a/src/containers/Header/breadcrumbs.tsx +++ b/src/containers/Header/breadcrumbs.tsx @@ -13,6 +13,7 @@ import type { NodeBreadcrumbsOptions, PDiskBreadcrumbsOptions, Page, + StorageGroupBreadcrumbsOptions, TabletBreadcrumbsOptions, TenantBreadcrumbsOptions, VDiskBreadcrumbsOptions, @@ -156,6 +157,27 @@ const getVDiskBreadcrumbs: GetBreadcrumbs = (options, q return breadcrumbs; }; +const getStorageGroupBreadcrumbs: GetBreadcrumbs = ( + options, + query = {}, +) => { + const {groupId} = options; + + const breadcrumbs = getClusterBreadcrumbs(options, query); + + let text = headerKeyset('breadcrumbs.storageGroup'); + if (groupId) { + text += ` ${groupId}`; + } + + const lastItem = { + text, + }; + breadcrumbs.push(lastItem); + + return breadcrumbs; +}; + const getTabletBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {tabletId, tabletType, nodeId, nodeRole, nodeActiveTab = TABLETS, tenantName} = options; @@ -178,6 +200,7 @@ const mapPageToGetter = { tablet: getTabletBreadcrumbs, tenant: getTenantBreadcrumbs, vDisk: getVDiskBreadcrumbs, + storageGroup: getStorageGroupBreadcrumbs, } as const; export const getBreadcrumbs = ( diff --git a/src/containers/Header/i18n/en.json b/src/containers/Header/i18n/en.json index 764477f69..c457ae341 100644 --- a/src/containers/Header/i18n/en.json +++ b/src/containers/Header/i18n/en.json @@ -4,5 +4,6 @@ "breadcrumbs.pDisk": "PDisk", "breadcrumbs.vDisk": "VDisk", "breadcrumbs.tablet": "Tablet", - "breadcrumbs.tablets": "Tablets" + "breadcrumbs.tablets": "Tablets", + "breadcrumbs.storageGroup": "Storage Group" } diff --git a/src/containers/PDiskPage/PDiskPage.scss b/src/containers/PDiskPage/PDiskPage.scss index fa3071ea1..1cc67d8f1 100644 --- a/src/containers/PDiskPage/PDiskPage.scss +++ b/src/containers/PDiskPage/PDiskPage.scss @@ -3,13 +3,24 @@ .ydb-pdisk-page { position: relative; - display: flex; overflow: auto; - flex-direction: column; - gap: 20px; height: 100%; - padding: 20px; + + &__info-content { + position: sticky; + left: 0; + + display: flex; + flex-direction: column; + gap: 20px; + + padding: 20px; + } + + &__tabs-content { + padding-left: 20px; + } &__meta, &__title, diff --git a/src/containers/PDiskPage/PDiskPage.tsx b/src/containers/PDiskPage/PDiskPage.tsx index 0f75bdc7b..88c302c9e 100644 --- a/src/containers/PDiskPage/PDiskPage.tsx +++ b/src/containers/PDiskPage/PDiskPage.tsx @@ -252,14 +252,16 @@ export function PDiskPage() { return (
- {renderHelmet()} - {renderPageMeta()} - {renderPageTitle()} - {renderControls()} - {renderError()} - {renderInfo()} - {renderTabs()} - {renderTabsContent()} +
+ {renderHelmet()} + {renderPageMeta()} + {renderPageTitle()} + {renderControls()} + {renderError()} + {renderInfo()} + {renderTabs()} +
+
{renderTabsContent()}
); } diff --git a/src/containers/Storage/StorageGroups/columns/getStorageGroupsColumns.tsx b/src/containers/Storage/StorageGroups/columns/getStorageGroupsColumns.tsx index e1d31cfbb..30f67547c 100644 --- a/src/containers/Storage/StorageGroups/columns/getStorageGroupsColumns.tsx +++ b/src/containers/Storage/StorageGroups/columns/getStorageGroupsColumns.tsx @@ -6,8 +6,10 @@ import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit'; import {CellWithPopover} from '../../../../components/CellWithPopover/CellWithPopover'; import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus'; +import {InternalLink} from '../../../../components/InternalLink'; import {UsageLabel} from '../../../../components/UsageLabel/UsageLabel'; import {VDiskWithDonorsStack} from '../../../../components/VDisk/VDiskWithDonorsStack'; +import {getStorageGroupPath} from '../../../../routes'; import {VISIBLE_ENTITIES} from '../../../../store/reducers/storage/constants'; import type {VisibleEntities} from '../../../../store/reducers/storage/types'; import type {NodesMap} from '../../../../types/store/nodesList'; @@ -139,7 +141,13 @@ const groupIdColumn: StorageGroupsColumn = { header: 'Group ID', width: 130, render: ({row}) => { - return {row.GroupId}; + return row.GroupId ? ( + + {row.GroupId} + + ) : ( + '-' + ); }, sortAccessor: (row) => Number(row.GroupId), align: DataTable.RIGHT, diff --git a/src/containers/StorageGroupPage/StorageGroupPage.scss b/src/containers/StorageGroupPage/StorageGroupPage.scss new file mode 100644 index 000000000..e82656ff4 --- /dev/null +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -0,0 +1,40 @@ +@import '../../styles/mixins.scss'; + +.ydb-storage-group-page { + position: relative; + + overflow: auto; + + height: 100%; + + &__info-content { + position: sticky; + left: 0; + + display: flex; + flex-direction: column; + gap: 20px; + + padding: 20px; + } + + &__tabs-content { + padding-left: 20px; + } + + &__meta, + &__title, + &__info, + &__tabs { + position: sticky; + left: 0; + } + + &__info { + margin-top: var(--g-spacing-10); + } + + &__tabs { + @include tabs-wrapper-styles(); + } +} diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx new file mode 100644 index 000000000..a49fd1400 --- /dev/null +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -0,0 +1,203 @@ +import React from 'react'; + +import {Tabs} from '@gravity-ui/uikit'; +import {skipToken} from '@reduxjs/toolkit/query'; +import {Helmet} from 'react-helmet-async'; +import {StringParam, useQueryParams} from 'use-query-params'; +import {z} from 'zod'; + +import {EntityPageTitle} from '../../components/EntityPageTitle/EntityPageTitle'; +import {ResponseError} from '../../components/Errors/ResponseError'; +import {InfoViewerSkeleton} from '../../components/InfoViewerSkeleton/InfoViewerSkeleton'; +import {InternalLink} from '../../components/InternalLink'; +import {PageMetaWithAutorefresh} from '../../components/PageMeta/PageMeta'; +import {StorageGroupInfo} from '../../components/StorageGroupInfo/StorageGroupInfo'; +import {getStorageGroupPath} from '../../routes'; +import {useStorageGroupsHandlerAvailable} from '../../store/reducers/capabilities/hooks'; +import {setHeaderBreadcrumbs} from '../../store/reducers/header/header'; +import {STORAGE_TYPES} from '../../store/reducers/storage/constants'; +import {storageApi} from '../../store/reducers/storage/storage'; +import {EFlag} from '../../types/api/enums'; +import {valueIsDefined} from '../../utils'; +import {cn} from '../../utils/cn'; +import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants'; +import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks'; +import {NodesUptimeFilterValues} from '../../utils/nodes'; +import {StorageGroups} from '../Storage/StorageGroups/StorageGroups'; +import {StorageNodes} from '../Storage/StorageNodes/StorageNodes'; + +import {storageGroupPageKeyset} from './i18n'; + +import './StorageGroupPage.scss'; + +const STORAGE_GROUP_PAGE_TABS = [ + { + id: STORAGE_TYPES.groups, + get title() { + return storageGroupPageKeyset('group'); + }, + }, + { + id: STORAGE_TYPES.nodes, + get title() { + return storageGroupPageKeyset('nodes'); + }, + }, +]; + +const storageGroupPageCn = cn('ydb-storage-group-page'); + +const storageGroupTabSchema = z.nativeEnum(STORAGE_TYPES).catch(STORAGE_TYPES.groups); + +export function StorageGroupPage() { + const dispatch = useTypedDispatch(); + + const [{groupId, activeTab}] = useQueryParams({ + groupId: StringParam, + activeTab: StringParam, + }); + + const storageGroupTab = storageGroupTabSchema.parse(activeTab); + + React.useEffect(() => { + dispatch(setHeaderBreadcrumbs('storageGroup', {groupId})); + }, [dispatch, groupId]); + + const [autoRefreshInterval] = useAutoRefreshInterval(); + const shouldUseGroupsHandler = useStorageGroupsHandlerAvailable(); + const groupQuery = storageApi.useGetStorageGroupsInfoQuery( + valueIsDefined(groupId) ? {groupId, shouldUseGroupsHandler} : skipToken, + { + pollingInterval: autoRefreshInterval, + }, + ); + + const nodesQuery = storageApi.useGetStorageNodesInfoQuery( + groupId && storageGroupTab === STORAGE_TYPES.nodes ? {group_id: groupId} : skipToken, + { + pollingInterval: autoRefreshInterval, + }, + ); + + const storageGroupData = groupQuery.data?.groups?.[0]; + const nodesData = nodesQuery.data?.nodes; + + const loading = groupQuery.isFetching && storageGroupData === undefined; + + const renderHelmet = () => { + const pageTitle = groupId + ? `${storageGroupPageKeyset('storage-group')} ${groupId}` + : storageGroupPageKeyset('storage-group'); + + return ( + + ); + }; + + const renderPageMeta = () => { + if (!groupId) { + return null; + } + + const items = [`${storageGroupPageKeyset('pool-name')}: ${storageGroupData?.PoolName}`]; + + return ( + + ); + }; + + const renderPageTitle = () => { + return ( + + ); + }; + + const renderInfo = () => { + if (loading) { + return ; + } + return ; + }; + + const renderTabs = () => { + return ( +
+ { + const path = groupId + ? getStorageGroupPath(groupId, {activeTab: id}) + : undefined; + + return ( + + {tabNode} + + ); + }} + /> +
+ ); + }; + + const renderTabsContent = () => { + switch (storageGroupTab) { + case 'groups': { + return storageGroupData ? ( + + ) : null; + } + case 'nodes': { + return nodesData ? ( + + ) : null; + } + default: + return null; + } + }; + + const renderError = () => { + if (!groupQuery.error && !nodesQuery.error) { + return null; + } + return ; + }; + + return ( +
+
+ {renderHelmet()} + {renderPageMeta()} + {renderPageTitle()} + {renderError()} + {renderInfo()} + {renderTabs()} +
+
{renderTabsContent()}
+
+ ); +} diff --git a/src/containers/StorageGroupPage/i18n/en.json b/src/containers/StorageGroupPage/i18n/en.json new file mode 100644 index 000000000..3f35d8fd3 --- /dev/null +++ b/src/containers/StorageGroupPage/i18n/en.json @@ -0,0 +1,6 @@ +{ + "storage-group": "Storage Group", + "group": "Group", + "nodes": "Nodes", + "pool-name": "Pool Name" +} diff --git a/src/containers/StorageGroupPage/i18n/index.ts b/src/containers/StorageGroupPage/i18n/index.ts new file mode 100644 index 000000000..faa3b05ad --- /dev/null +++ b/src/containers/StorageGroupPage/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-storage-group-page'; + +export const storageGroupPageKeyset = registerKeysets(COMPONENT, {en}); diff --git a/src/routes.ts b/src/routes.ts index 6fdffe74d..17b10a5da 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -11,6 +11,7 @@ export const TENANT = 'tenant'; export const NODE = 'node'; export const PDISK = 'pDisk'; export const VDISK = 'vDisk'; +export const STORAGE_GROUP = 'storageGroup'; export const TABLET = 'tablet'; const routes = { @@ -20,6 +21,7 @@ const routes = { node: `/${NODE}/:id/:activeTab?`, pDisk: `/${PDISK}`, vDisk: `/${VDISK}`, + storageGroup: `/${STORAGE_GROUP}`, tablet: `/${TABLET}/:id`, tabletsFilters: `/tabletsFilters`, auth: `/auth`, @@ -106,6 +108,10 @@ export function getVDiskPagePath( return createHref(routes.vDisk, undefined, {...query, nodeId, pDiskId, vDiskSlotId}); } +export function getStorageGroupPath(groupId: string | number, query: Query = {}) { + return createHref(routes.storageGroup, undefined, {...query, groupId}); +} + export function getTabletPagePath(tabletId: string | number, query: Query = {}) { return createHref(routes.tablet, {id: tabletId}, {...query}); } diff --git a/src/store/reducers/header/types.ts b/src/store/reducers/header/types.ts index 8d487b117..7cf75a480 100644 --- a/src/store/reducers/header/types.ts +++ b/src/store/reducers/header/types.ts @@ -4,7 +4,15 @@ import type {EType} from '../../../types/api/tablet'; import type {setHeaderBreadcrumbs} from './header'; -export type Page = 'cluster' | 'tenant' | 'node' | 'pDisk' | 'vDisk' | 'tablet' | undefined; +export type Page = + | 'cluster' + | 'tenant' + | 'node' + | 'pDisk' + | 'vDisk' + | 'tablet' + | 'storageGroup' + | undefined; export interface ClusterBreadcrumbsOptions { clusterName?: string; @@ -15,6 +23,10 @@ export interface TenantBreadcrumbsOptions extends ClusterBreadcrumbsOptions { tenantName?: string; } +export interface StorageGroupBreadcrumbsOptions extends ClusterBreadcrumbsOptions { + groupId?: string; +} + export interface NodeBreadcrumbsOptions extends TenantBreadcrumbsOptions { nodeId?: string | number; nodeActiveTab?: NodeTab; @@ -38,7 +50,8 @@ export type BreadcrumbsOptions = | ClusterBreadcrumbsOptions | TenantBreadcrumbsOptions | NodeBreadcrumbsOptions - | TabletBreadcrumbsOptions; + | TabletBreadcrumbsOptions + | StorageGroupBreadcrumbsOptions; export type PageBreadcrumbsOptions = T extends 'cluster' ? ClusterBreadcrumbsOptions diff --git a/src/store/reducers/nodes/types.ts b/src/store/reducers/nodes/types.ts index 9b6f5f58b..e8a3a270d 100644 --- a/src/store/reducers/nodes/types.ts +++ b/src/store/reducers/nodes/types.ts @@ -64,6 +64,7 @@ export interface NodesGeneralRequestParams extends Partial { export interface NodesApiRequestParams extends NodesGeneralRequestParams { node_id?: number | string; // get only specific node + group_id?: number | string; path?: string; database?: string; /** @deprecated use database instead */ diff --git a/src/store/reducers/storage/types.ts b/src/store/reducers/storage/types.ts index 84dc6e2bf..d725d5b56 100644 --- a/src/store/reducers/storage/types.ts +++ b/src/store/reducers/storage/types.ts @@ -46,6 +46,7 @@ export interface PreparedStorageGroup { Encryption?: boolean; ErasureSpecies?: string; Degraded: number; + Overall?: EFlag; GroupId?: string | number; @@ -58,6 +59,26 @@ export interface PreparedStorageGroup { DiskSpace: EFlag; VDisks?: PreparedVDisk[]; + + Kind?: string; + ChangeTime?: number | string; + GroupGeneration?: string; + Latency?: EFlag; + AcquiredUnits?: string; + AcquiredIOPS?: number; + AcquiredThroughput?: string; + AcquiredSize?: string; + MaximumIOPS?: number; + MaximumThroughput?: string; + MaximumSize?: string; + + AllocationUnits?: string | number; + State?: string; + MissingDisks?: string | number; + Available?: string; + LatencyPutTabletLog?: number; + LatencyPutUserData?: number; + LatencyGetFast?: number; } export interface UsageFilter { diff --git a/src/store/reducers/storage/utils.ts b/src/store/reducers/storage/utils.ts index d3eb08ce3..bc2348f2a 100644 --- a/src/store/reducers/storage/utils.ts +++ b/src/store/reducers/storage/utils.ts @@ -98,7 +98,9 @@ export const prepareStorageGroupData = ( return { ...group, + GroupGeneration: group.GroupGeneration ? String(group.GroupGeneration) : undefined, GroupId: group.GroupID, + Overall: group.Overall, VDisks: vDisks, Usage: usage, Read: readSpeedBytesPerSec, @@ -125,6 +127,8 @@ export const prepareStorageGroupDataV2 = (group: TStorageGroupInfoV2): PreparedS Kind, MediaType, GroupID, + Overall, + GroupGeneration, } = group; const vDisks = VDisks.map((vdisk) => prepareVDisk(vdisk, PoolName)); @@ -139,6 +143,8 @@ export const prepareStorageGroupDataV2 = (group: TStorageGroupInfoV2): PreparedS MediaType: MediaType || Kind, VDisks: vDisks, Usage: usage, + Overall, + GroupGeneration: GroupGeneration ? String(GroupGeneration) : undefined, Read: Number(Read), Write: Number(Write), Used: Number(Used), @@ -225,7 +231,7 @@ export const prepareStorageResponse = (data: TStorageInfo): PreparedStorageRespo export function prepareGroupsResponse(data: StorageGroupsResponse): PreparedStorageResponse { const {FoundGroups, TotalGroups, StorageGroups = []} = data; const preparedGroups: PreparedStorageGroup[] = StorageGroups.map((group) => { - const {Usage, Read, Write, Used, Limit, MissingDisks, VDisks = []} = group; + const {Usage, Read, Write, Used, Limit, MissingDisks, VDisks = [], Overall} = group; const vDisks = VDisks.map((disk) => { const whiteboardVDisk = disk.Whiteboard; @@ -248,13 +254,13 @@ export function prepareGroupsResponse(data: StorageGroupsResponse): PreparedStor return { ...group, - Usage: Math.floor(Number(Usage)) || 0, Read: Number(Read), Write: Number(Write), Used: Number(Used), Limit: Number(Limit), Degraded: Number(MissingDisks), + Overall, VDisks: vDisks,