From 016708dff10ac7843baff4113db177e301b63088 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 12 Sep 2024 16:07:16 +0300 Subject: [PATCH 1/8] feat: add storage group page --- .../StorageGroupInfo/StorageGroupInfo.scss | 7 + .../StorageGroupInfo/StorageGroupInfo.tsx | 188 ++++++++++++++++ src/components/StorageGroupInfo/i18n/en.json | 32 +++ 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 +- .../columns/getStorageGroupsColumns.tsx | 10 +- .../StorageGroupPage/StorageGroupPage.scss | 44 ++++ .../StorageGroupPage/StorageGroupPage.tsx | 201 ++++++++++++++++++ src/containers/StorageGroupPage/i18n/en.json | 5 + src/containers/StorageGroupPage/i18n/index.ts | 7 + src/routes.ts | 6 + src/store/reducers/header/types.ts | 17 +- src/store/reducers/storage/types.ts | 21 ++ src/store/reducers/storage/utils.ts | 10 +- 17 files changed, 591 insertions(+), 6 deletions(-) create mode 100644 src/components/StorageGroupInfo/StorageGroupInfo.scss 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/StorageGroupInfo/StorageGroupInfo.scss b/src/components/StorageGroupInfo/StorageGroupInfo.scss new file mode 100644 index 000000000..f95a98773 --- /dev/null +++ b/src/components/StorageGroupInfo/StorageGroupInfo.scss @@ -0,0 +1,7 @@ +.ydb-storage-group-info { + &__wrapper { + display: flex; + flex-flow: row wrap; + gap: 7px; + } +} diff --git a/src/components/StorageGroupInfo/StorageGroupInfo.tsx b/src/components/StorageGroupInfo/StorageGroupInfo.tsx new file mode 100644 index 000000000..dd145f1ba --- /dev/null +++ b/src/components/StorageGroupInfo/StorageGroupInfo.tsx @@ -0,0 +1,188 @@ +import type {PreparedStorageGroup} from '../../store/reducers/storage/types'; +import {valueIsDefined} from '../../utils'; +import {cn} from '../../utils/cn'; +import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters'; +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'; + +import './StorageGroupInfo.scss'; + +const b = cn('ydb-storage-group-info'); + +interface StorageGroupInfoProps extends Omit { + data?: PreparedStorageGroup; + className?: string; +} + +// eslint-disable-next-line complexity +export function StorageGroupInfo({data, className, ...infoViewerProps}: StorageGroupInfoProps) { + const { + GroupId, + PoolName, + Encryption, + Overall, + DiskSpace, + MediaType, + ErasureSpecies, + Used, + Limit, + Usage, + Read, + Write, + GroupGeneration, + Latency, + AllocationUnits, + State, + MissingDisks, + Available, + LatencyPutTabletLog, + LatencyPutUserData, + LatencyGetFast, + } = data || {}; + + const storageGroupInfoFirstColumn = []; + + if (valueIsDefined(GroupId)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('group-id'), + value: GroupId, + }); + } + if (valueIsDefined(GroupGeneration)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('group-generation'), + value: GroupGeneration, + }); + } + if (valueIsDefined(PoolName)) { + storageGroupInfoFirstColumn.push({ + label: storageGroupInfoKeyset('pool-name'), + value: PoolName, + }); + } + 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: `${LatencyPutTabletLog} ms`, + }); + } + if (valueIsDefined(LatencyPutUserData)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('latency-put-user-data'), + value: `${LatencyPutUserData} ms`, + }); + } + if (valueIsDefined(LatencyGetFast)) { + storageGroupInfoSecondColumn.push({ + label: storageGroupInfoKeyset('latency-get-fast'), + value: `${LatencyGetFast} ms`, + }); + } + 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..42164984d --- /dev/null +++ b/src/components/StorageGroupInfo/i18n/en.json @@ -0,0 +1,32 @@ +{ + "group-id": "Group ID", + "pool-name": "Pool Name", + "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", + "links": "Links", + "storage-group-page": "Storage Group Page", + "developer-ui": "Developer UI", + "yes": "Yes", + "no": "No", + "storage-group-title": "Storage Group", + "kind": "Kind", + "degraded": "Degraded", + "change-time": "Change Time", + "group-generation": "Group Generation", + "latency": "Latency", + "acquired-units": "Units", + "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/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..7cb7c69ab --- /dev/null +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -0,0 +1,44 @@ +@import '../../styles//mixins.scss'; + +.ydb-storage-group-page { + position: relative; + + display: flex; + flex-direction: column; + gap: 20px; + + height: 100%; + padding: 20px; + + &__meta, + &__title, + &__controls, + &__info, + &__tabs { + position: sticky; + left: 0; + } + + &__controls { + display: flex; + align-items: center; + gap: var(--g-spacing-2); + } + + &__group-disks { + display: flex; + flex-grow: 1; + flex-flow: row wrap; + gap: 10px; + + margin-top: 20px; + } + + &__tabs { + @include tabs-wrapper-styles(); + } + + &__group-disk { + width: 150px; + } +} diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx new file mode 100644 index 000000000..850c46754 --- /dev/null +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -0,0 +1,201 @@ +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( + storageGroupTab === STORAGE_TYPES.nodes ? {} : 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('storage-group')}: ${groupId}`]; + + 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..e08824251 --- /dev/null +++ b/src/containers/StorageGroupPage/i18n/en.json @@ -0,0 +1,5 @@ +{ + "storage-group": "Storage Group", + "group": "Group", + "nodes": "Nodes" +} 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/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, From 6f7b80011d63c6dfa56bd5da8fd25f81d5437fce Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 12 Sep 2024 16:31:57 +0300 Subject: [PATCH 2/8] fix: add group_id filter --- src/containers/StorageGroupPage/StorageGroupPage.tsx | 2 +- src/store/reducers/nodes/types.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx index 850c46754..e68573645 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.tsx +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -73,7 +73,7 @@ export function StorageGroupPage() { ); const nodesQuery = storageApi.useGetStorageNodesInfoQuery( - storageGroupTab === STORAGE_TYPES.nodes ? {} : skipToken, + groupId && storageGroupTab === STORAGE_TYPES.nodes ? {group_id: groupId} : skipToken, { pollingInterval: autoRefreshInterval, }, diff --git a/src/store/reducers/nodes/types.ts b/src/store/reducers/nodes/types.ts index 4f970cf90..6b8cc90a3 100644 --- a/src/store/reducers/nodes/types.ts +++ b/src/store/reducers/nodes/types.ts @@ -68,6 +68,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 */ From 225628829050c49a7cb93be0c695a45ac4442807 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 12 Sep 2024 16:38:11 +0300 Subject: [PATCH 3/8] fix: overflow for table --- src/containers/StorageGroupPage/StorageGroupPage.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/containers/StorageGroupPage/StorageGroupPage.scss b/src/containers/StorageGroupPage/StorageGroupPage.scss index 7cb7c69ab..e798f23b3 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.scss +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -4,6 +4,7 @@ position: relative; display: flex; + overflow: auto; flex-direction: column; gap: 20px; From 21c1e4c061520c3aa02bd67bb9a9aaea826c240b Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 13 Sep 2024 16:25:16 +0300 Subject: [PATCH 4/8] fix: design fixes --- .../StorageGroupInfo/StorageGroupInfo.tsx | 14 -------------- src/components/StorageGroupInfo/i18n/en.json | 1 - .../StorageGroupPage/StorageGroupPage.scss | 4 ++++ .../StorageGroupPage/StorageGroupPage.tsx | 2 +- src/containers/StorageGroupPage/i18n/en.json | 3 ++- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/components/StorageGroupInfo/StorageGroupInfo.tsx b/src/components/StorageGroupInfo/StorageGroupInfo.tsx index dd145f1ba..0944102a9 100644 --- a/src/components/StorageGroupInfo/StorageGroupInfo.tsx +++ b/src/components/StorageGroupInfo/StorageGroupInfo.tsx @@ -22,8 +22,6 @@ interface StorageGroupInfoProps extends Omit { // eslint-disable-next-line complexity export function StorageGroupInfo({data, className, ...infoViewerProps}: StorageGroupInfoProps) { const { - GroupId, - PoolName, Encryption, Overall, DiskSpace, @@ -47,24 +45,12 @@ export function StorageGroupInfo({data, className, ...infoViewerProps}: StorageG const storageGroupInfoFirstColumn = []; - if (valueIsDefined(GroupId)) { - storageGroupInfoFirstColumn.push({ - label: storageGroupInfoKeyset('group-id'), - value: GroupId, - }); - } if (valueIsDefined(GroupGeneration)) { storageGroupInfoFirstColumn.push({ label: storageGroupInfoKeyset('group-generation'), value: GroupGeneration, }); } - if (valueIsDefined(PoolName)) { - storageGroupInfoFirstColumn.push({ - label: storageGroupInfoKeyset('pool-name'), - value: PoolName, - }); - } if (valueIsDefined(ErasureSpecies)) { storageGroupInfoFirstColumn.push({ label: storageGroupInfoKeyset('erasure-species'), diff --git a/src/components/StorageGroupInfo/i18n/en.json b/src/components/StorageGroupInfo/i18n/en.json index 42164984d..f49732e08 100644 --- a/src/components/StorageGroupInfo/i18n/en.json +++ b/src/components/StorageGroupInfo/i18n/en.json @@ -1,5 +1,4 @@ { - "group-id": "Group ID", "pool-name": "Pool Name", "encryption": "Encryption", "overall": "Overall", diff --git a/src/containers/StorageGroupPage/StorageGroupPage.scss b/src/containers/StorageGroupPage/StorageGroupPage.scss index e798f23b3..8097610f2 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.scss +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -20,6 +20,10 @@ left: 0; } + &__info { + margin-top: var(--g-spacing-10); + } + &__controls { display: flex; align-items: center; diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx index e68573645..6ae9e4786 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.tsx +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -102,7 +102,7 @@ export function StorageGroupPage() { return null; } - const items = [`${storageGroupPageKeyset('storage-group')}: ${groupId}`]; + const items = [`${storageGroupPageKeyset('pool-name')}: ${storageGroupData?.PoolName}`]; return ( Date: Mon, 16 Sep 2024 15:52:59 +0300 Subject: [PATCH 5/8] fix: review fixes --- src/components/StorageGroupInfo/i18n/en.json | 9 ------- .../StorageGroupPage/StorageGroupPage.scss | 24 +------------------ 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/components/StorageGroupInfo/i18n/en.json b/src/components/StorageGroupInfo/i18n/en.json index f49732e08..8357006f3 100644 --- a/src/components/StorageGroupInfo/i18n/en.json +++ b/src/components/StorageGroupInfo/i18n/en.json @@ -1,5 +1,4 @@ { - "pool-name": "Pool Name", "encryption": "Encryption", "overall": "Overall", "disk-space": "Disk Space", @@ -9,18 +8,10 @@ "usage": "Usage", "read-throughput": "Read Throughput", "write-throughput": "Write Throughput", - "links": "Links", - "storage-group-page": "Storage Group Page", - "developer-ui": "Developer UI", "yes": "Yes", "no": "No", - "storage-group-title": "Storage Group", - "kind": "Kind", - "degraded": "Degraded", - "change-time": "Change Time", "group-generation": "Group Generation", "latency": "Latency", - "acquired-units": "Units", "allocation-units": "Units", "state": "State", "missing-disks": "Missing Disks", diff --git a/src/containers/StorageGroupPage/StorageGroupPage.scss b/src/containers/StorageGroupPage/StorageGroupPage.scss index 8097610f2..2a312994c 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.scss +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -1,19 +1,16 @@ -@import '../../styles//mixins.scss'; +@import '../../styles/mixins.scss'; .ydb-storage-group-page { position: relative; - display: flex; overflow: auto; flex-direction: column; gap: 20px; - height: 100%; padding: 20px; &__meta, &__title, - &__controls, &__info, &__tabs { position: sticky; @@ -24,26 +21,7 @@ margin-top: var(--g-spacing-10); } - &__controls { - display: flex; - align-items: center; - gap: var(--g-spacing-2); - } - - &__group-disks { - display: flex; - flex-grow: 1; - flex-flow: row wrap; - gap: 10px; - - margin-top: 20px; - } - &__tabs { @include tabs-wrapper-styles(); } - - &__group-disk { - width: 150px; - } } From c6727c595eef68d7b449166ef85fef8d2ac8e24a Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Mon, 16 Sep 2024 15:56:44 +0300 Subject: [PATCH 6/8] fix: lint --- src/containers/StorageGroupPage/StorageGroupPage.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/containers/StorageGroupPage/StorageGroupPage.scss b/src/containers/StorageGroupPage/StorageGroupPage.scss index 2a312994c..0940f1d6f 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.scss +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -2,10 +2,12 @@ .ydb-storage-group-page { position: relative; + display: flex; overflow: auto; flex-direction: column; gap: 20px; + height: 100%; padding: 20px; From 6748c2d1a1c095fe778a2e207bc2ccc43028434e Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Tue, 17 Sep 2024 13:47:26 +0300 Subject: [PATCH 7/8] fix: fix sticky header --- src/containers/PDiskPage/PDiskPage.scss | 19 +++++++++++++------ src/containers/PDiskPage/PDiskPage.tsx | 18 ++++++++++-------- .../StorageGroupPage/StorageGroupPage.scss | 19 +++++++++++++------ .../StorageGroupPage/StorageGroupPage.tsx | 18 ++++++++++-------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/containers/PDiskPage/PDiskPage.scss b/src/containers/PDiskPage/PDiskPage.scss index fa3071ea1..f0c05d42a 100644 --- a/src/containers/PDiskPage/PDiskPage.scss +++ b/src/containers/PDiskPage/PDiskPage.scss @@ -2,14 +2,21 @@ .ydb-pdisk-page { position: relative; - - display: flex; overflow: auto; - flex-direction: column; - gap: 20px; - height: 100%; - padding: 20px; + + &__info-content { + display: flex; + flex-direction: column; + padding: 20px; + gap: 20px; + position: sticky; + left: 0; + } + + &__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/StorageGroupPage/StorageGroupPage.scss b/src/containers/StorageGroupPage/StorageGroupPage.scss index 0940f1d6f..127b5ec71 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.scss +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -2,14 +2,21 @@ .ydb-storage-group-page { position: relative; - - display: flex; overflow: auto; - flex-direction: column; - gap: 20px; - height: 100%; - padding: 20px; + + &__info-content { + display: flex; + flex-direction: column; + padding: 20px; + gap: 20px; + position: sticky; + left: 0; + } + + &__tabs-content { + padding-left: 20px; + } &__meta, &__title, diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx index 6ae9e4786..a49fd1400 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.tsx +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -188,14 +188,16 @@ export function StorageGroupPage() { }; return ( -
- {renderHelmet()} - {renderPageMeta()} - {renderPageTitle()} - {renderError()} - {renderInfo()} - {renderTabs()} - {renderTabsContent()} +
+
+ {renderHelmet()} + {renderPageMeta()} + {renderPageTitle()} + {renderError()} + {renderInfo()} + {renderTabs()} +
+
{renderTabsContent()}
); } From 546bdebfc13edc97b801e37554b5abdeebe1b7e6 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Tue, 17 Sep 2024 14:21:55 +0300 Subject: [PATCH 8/8] fix: review fixes --- src/components/PDiskInfo/PDiskInfo.scss | 14 ---------- src/components/PDiskInfo/PDiskInfo.tsx | 14 +++++----- .../StorageGroupInfo/StorageGroupInfo.scss | 7 ----- .../StorageGroupInfo/StorageGroupInfo.tsx | 26 +++++++------------ src/containers/PDiskPage/PDiskPage.scss | 10 ++++--- .../StorageGroupPage/StorageGroupPage.scss | 10 ++++--- 6 files changed, 32 insertions(+), 49 deletions(-) delete mode 100644 src/components/StorageGroupInfo/StorageGroupInfo.scss 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.scss b/src/components/StorageGroupInfo/StorageGroupInfo.scss deleted file mode 100644 index f95a98773..000000000 --- a/src/components/StorageGroupInfo/StorageGroupInfo.scss +++ /dev/null @@ -1,7 +0,0 @@ -.ydb-storage-group-info { - &__wrapper { - display: flex; - flex-flow: row wrap; - gap: 7px; - } -} diff --git a/src/components/StorageGroupInfo/StorageGroupInfo.tsx b/src/components/StorageGroupInfo/StorageGroupInfo.tsx index 0944102a9..2db2eab63 100644 --- a/src/components/StorageGroupInfo/StorageGroupInfo.tsx +++ b/src/components/StorageGroupInfo/StorageGroupInfo.tsx @@ -1,7 +1,9 @@ +import {Flex} from '@gravity-ui/uikit'; + import type {PreparedStorageGroup} from '../../store/reducers/storage/types'; import {valueIsDefined} from '../../utils'; -import {cn} from '../../utils/cn'; 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'; @@ -10,10 +12,6 @@ import {ProgressViewer} from '../ProgressViewer/ProgressViewer'; import {storageGroupInfoKeyset} from './i18n'; -import './StorageGroupInfo.scss'; - -const b = cn('ydb-storage-group-info'); - interface StorageGroupInfoProps extends Omit { data?: PreparedStorageGroup; className?: string; @@ -127,19 +125,19 @@ export function StorageGroupInfo({data, className, ...infoViewerProps}: StorageG if (valueIsDefined(LatencyPutTabletLog)) { storageGroupInfoSecondColumn.push({ label: storageGroupInfoKeyset('latency-put-tablet-log'), - value: `${LatencyPutTabletLog} ms`, + value: formatToMs(LatencyPutTabletLog), }); } if (valueIsDefined(LatencyPutUserData)) { storageGroupInfoSecondColumn.push({ label: storageGroupInfoKeyset('latency-put-user-data'), - value: `${LatencyPutUserData} ms`, + value: formatToMs(LatencyPutUserData), }); } if (valueIsDefined(LatencyGetFast)) { storageGroupInfoSecondColumn.push({ label: storageGroupInfoKeyset('latency-get-fast'), - value: `${LatencyGetFast} ms`, + value: formatToMs(LatencyGetFast), }); } if (valueIsDefined(AllocationUnits)) { @@ -162,13 +160,9 @@ export function StorageGroupInfo({data, className, ...infoViewerProps}: StorageG } return ( -
-
- -
-
- -
-
+ + + + ); } diff --git a/src/containers/PDiskPage/PDiskPage.scss b/src/containers/PDiskPage/PDiskPage.scss index f0c05d42a..1cc67d8f1 100644 --- a/src/containers/PDiskPage/PDiskPage.scss +++ b/src/containers/PDiskPage/PDiskPage.scss @@ -2,16 +2,20 @@ .ydb-pdisk-page { position: relative; + overflow: auto; + height: 100%; &__info-content { + position: sticky; + left: 0; + display: flex; flex-direction: column; - padding: 20px; gap: 20px; - position: sticky; - left: 0; + + padding: 20px; } &__tabs-content { diff --git a/src/containers/StorageGroupPage/StorageGroupPage.scss b/src/containers/StorageGroupPage/StorageGroupPage.scss index 127b5ec71..e82656ff4 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.scss +++ b/src/containers/StorageGroupPage/StorageGroupPage.scss @@ -2,16 +2,20 @@ .ydb-storage-group-page { position: relative; + overflow: auto; + height: 100%; &__info-content { + position: sticky; + left: 0; + display: flex; flex-direction: column; - padding: 20px; gap: 20px; - position: sticky; - left: 0; + + padding: 20px; } &__tabs-content {