From 4a2b4ffd9acdef5e78a894158de178560067cd88 Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Mon, 9 Sep 2024 15:36:54 +0300 Subject: [PATCH 1/6] feat: use new /viewer/cluster handler format --- .../DiskStateProgressBar.scss | 43 +-- src/components/Tag/Tag.tsx | 4 +- src/components/Tags/Tags.tsx | 4 +- .../Cluster/ClusterInfo/ClusterInfo.scss | 21 +- .../Cluster/ClusterInfo/ClusterInfo.tsx | 203 +-------------- .../DiskGroupsStatsBars.scss | 11 + .../DiskGroupsStatsBars.tsx | 78 ++++++ .../components/NodesState/NodesState.scss | 16 ++ .../components/NodesState/NodesState.tsx | 15 ++ src/containers/Cluster/ClusterInfo/shared.ts | 3 + src/containers/Cluster/ClusterInfo/utils.ts | 14 - src/containers/Cluster/ClusterInfo/utils.tsx | 244 ++++++++++++++++++ src/containers/Cluster/i18n/en.json | 8 +- src/containers/Cluster/i18n/index.ts | 3 +- src/containers/Cluster/i18n/ru.json | 16 -- src/store/reducers/cluster/cluster.ts | 16 +- src/store/reducers/cluster/utils.ts | 21 +- src/styles/mixins.scss | 45 ++++ src/types/api/cluster.ts | 34 ++- src/types/api/meta.ts | 4 +- .../versions/parseNodesToVersionsValues.ts | 4 +- 21 files changed, 514 insertions(+), 293 deletions(-) create mode 100644 src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.scss create mode 100644 src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.tsx create mode 100644 src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.scss create mode 100644 src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.tsx create mode 100644 src/containers/Cluster/ClusterInfo/shared.ts delete mode 100644 src/containers/Cluster/ClusterInfo/utils.ts create mode 100644 src/containers/Cluster/ClusterInfo/utils.tsx delete mode 100644 src/containers/Cluster/i18n/ru.json diff --git a/src/components/DiskStateProgressBar/DiskStateProgressBar.scss b/src/components/DiskStateProgressBar/DiskStateProgressBar.scss index ae50905501..cdb2a2074e 100644 --- a/src/components/DiskStateProgressBar/DiskStateProgressBar.scss +++ b/src/components/DiskStateProgressBar/DiskStateProgressBar.scss @@ -1,3 +1,5 @@ +@import '../../styles/mixins'; + .storage-disk-progress-bar { $block: &; @@ -5,9 +7,6 @@ $outer-border-radius: 4px; $inner-border-radius: $outer-border-radius - $border-width; - --progress-bar-border-color: var(--g-color-base-misc-heavy); - --progress-bar-background-color: var(--g-color-base-misc-light); - --progress-bar-fill-color: var(--g-color-base-misc-medium); --progress-bar-full-height: var(--g-text-body-3-line-height); --progress-bar-compact-height: 12px; @@ -20,9 +19,10 @@ text-align: center; color: var(--g-color-text-primary); - border: $border-width solid var(--progress-bar-border-color); + border: $border-width solid var(--entity-state-border-color); border-radius: $outer-border-radius; - background-color: var(--progress-bar-background-color); + background-color: var(--entity-state-background-color); + @include entity-state-colors(); &_compact { min-width: 0; @@ -31,35 +31,6 @@ border-radius: 2px; } - &_green { - --progress-bar-border-color: var(--g-color-base-positive-heavy); - --progress-bar-background-color: var(--g-color-base-positive-light); - --progress-bar-fill-color: var(--g-color-base-positive-medium); - } - - &_blue { - --progress-bar-border-color: var(--g-color-base-info-heavy); - --progress-bar-background-color: var(--g-color-base-info-light); - --progress-bar-fill-color: var(--g-color-base-info-medium); - } - - &_yellow { - --progress-bar-border-color: var(--g-color-base-warning-heavy); - --progress-bar-background-color: var(--g-color-base-yellow-light); - --progress-bar-fill-color: var(--g-color-base-yellow-medium); - } - - &_orange { - --progress-bar-border-color: var(--ydb-color-status-orange); - --progress-bar-background-color: var(--g-color-base-warning-light); - --progress-bar-fill-color: var(--g-color-base-warning-medium); - } - &_red { - --progress-bar-border-color: var(--g-color-base-danger-heavy); - --progress-bar-background-color: var(--g-color-base-danger-light); - --progress-bar-fill-color: var(--g-color-base-danger-medium); - } - &_faded { background-color: unset; } @@ -78,11 +49,11 @@ height: 100%; border-radius: $inner-border-radius 0 0 $inner-border-radius; - background-color: var(--progress-bar-fill-color); + background-color: var(--entity-state-fill-color); &_faded { // Bg color is light variant, use it to make bar less bright - background-color: var(--progress-bar-background-color); + background-color: var(--entity-state-background-color); } &_compact { diff --git a/src/components/Tag/Tag.tsx b/src/components/Tag/Tag.tsx index 241e91d1b1..bd28065543 100644 --- a/src/components/Tag/Tag.tsx +++ b/src/components/Tag/Tag.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import {cn} from '../../utils/cn'; import './Tag.scss'; @@ -7,7 +9,7 @@ const b = cn('tag'); export type TagType = 'blue'; interface TagProps { - text: string; + text: React.ReactNode; type?: TagType; } diff --git a/src/components/Tags/Tags.tsx b/src/components/Tags/Tags.tsx index ee0054dfbc..9a0c61d2b3 100644 --- a/src/components/Tags/Tags.tsx +++ b/src/components/Tags/Tags.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import {cn} from '../../utils/cn'; import type {TagType} from '../Tag'; import {Tag} from '../Tag'; @@ -7,7 +9,7 @@ import './Tags.scss'; const b = cn('tags'); interface TagsProps { - tags: string[]; + tags: React.ReactNode[]; tagsType?: TagType; className?: string; } diff --git a/src/containers/Cluster/ClusterInfo/ClusterInfo.scss b/src/containers/Cluster/ClusterInfo/ClusterInfo.scss index b4ebe35a26..e8e3d29449 100644 --- a/src/containers/Cluster/ClusterInfo/ClusterInfo.scss +++ b/src/containers/Cluster/ClusterInfo/ClusterInfo.scss @@ -47,24 +47,17 @@ gap: var(--g-spacing-2); } - &__storage-groups-stats { - display: flex; - flex-direction: column; - gap: 11px; - } - - &__groups-stats-bar { - cursor: pointer; - } - - &__groups-stats-popup-content { - padding: 12px; - } - &__clipboard-button { display: flex; align-items: center; margin-left: 5px; } + &__dc-count { + text-transform: lowercase; + } + &__nodes-states { + display: flex; + gap: var(--g-spacing-half); + } } diff --git a/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx b/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx index 857e386628..c777365e12 100644 --- a/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx +++ b/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx @@ -1,198 +1,20 @@ -import React from 'react'; - -import {ContentWithPopup} from '../../../components/ContentWithPopup/ContentWithPopup'; import {ResponseError} from '../../../components/Errors/ResponseError'; -import type {InfoViewerItem} from '../../../components/InfoViewer/InfoViewer'; import {InfoViewer} from '../../../components/InfoViewer/InfoViewer'; import {InfoViewerSkeleton} from '../../../components/InfoViewerSkeleton/InfoViewerSkeleton'; -import {LinkWithIcon} from '../../../components/LinkWithIcon/LinkWithIcon'; -import {ProgressViewer} from '../../../components/ProgressViewer/ProgressViewer'; -import {Tablet} from '../../../components/Tablet'; -import {Tags} from '../../../components/Tags'; import {backend, customBackend} from '../../../store'; -import type { - ClusterGroupsStats, - DiskErasureGroupsStats, - DiskGroupsStats, -} from '../../../store/reducers/cluster/types'; -import {nodesApi} from '../../../store/reducers/nodes/nodes'; -import type {AdditionalClusterProps, ClusterLink} from '../../../types/additionalProps'; +import type {ClusterGroupsStats} from '../../../store/reducers/cluster/types'; +import type {AdditionalClusterProps} from '../../../types/additionalProps'; import type {TClusterInfo} from '../../../types/api/cluster'; import type {IResponseError} from '../../../types/api/error'; -import type {VersionToColorMap, VersionValue} from '../../../types/versions'; -import {formatBytes, getSizeWithSignificantDigits} from '../../../utils/bytesParsers'; -import {cn} from '../../../utils/cn'; +import type {VersionToColorMap} from '../../../types/versions'; import {DEVELOPER_UI_TITLE} from '../../../utils/constants'; -import {formatStorageValues} from '../../../utils/dataFormatters/dataFormatters'; import {useTypedSelector} from '../../../utils/hooks'; -import {parseNodeGroupsToVersionsValues, parseNodesToVersionsValues} from '../../../utils/versions'; -import {VersionsBar} from '../VersionsBar/VersionsBar'; -import i18n from '../i18n'; -import {compareTablets} from './utils'; +import {b} from './shared'; +import {getInfo, useGetVersionValues} from './utils'; import './ClusterInfo.scss'; -const b = cn('cluster-info'); - -interface GroupsStatsPopupContentProps { - stats: DiskErasureGroupsStats; -} - -const GroupsStatsPopupContent = ({stats}: GroupsStatsPopupContentProps) => { - const {diskType, erasure, allocatedSize, availableSize} = stats; - - const sizeToConvert = getSizeWithSignificantDigits(Math.max(allocatedSize, availableSize), 2); - - const convertedAllocatedSize = formatBytes({value: allocatedSize, size: sizeToConvert}); - const convertedAvailableSize = formatBytes({value: availableSize, size: sizeToConvert}); - - const usage = Math.round((allocatedSize / (allocatedSize + availableSize)) * 100); - - const info = [ - { - label: i18n('disk-type'), - value: diskType, - }, - { - label: i18n('erasure'), - value: erasure, - }, - { - label: i18n('allocated'), - value: convertedAllocatedSize, - }, - { - label: i18n('available'), - value: convertedAvailableSize, - }, - { - label: i18n('usage'), - value: usage + '%', - }, - ]; - - return ( - - ); -}; - -interface DiskGroupsStatsProps { - stats: DiskGroupsStats; -} - -const DiskGroupsStatsBars = ({stats}: DiskGroupsStatsProps) => { - return ( -
- {Object.values(stats).map((erasureStats) => ( - } - > - - - ))} -
- ); -}; - -const getGroupsStatsFields = (groupsStats: ClusterGroupsStats) => { - return Object.keys(groupsStats).map((diskType) => { - return { - label: i18n('storage-groups', {diskType}), - value: , - }; - }); -}; - -const getInfo = ( - cluster: TClusterInfo, - versionsValues: VersionValue[], - groupsStats: ClusterGroupsStats, - additionalInfo: InfoViewerItem[], - links: ClusterLink[], -) => { - const info: InfoViewerItem[] = []; - - if (cluster.DataCenters) { - info.push({ - label: i18n('dc'), - value: , - }); - } - - if (cluster.SystemTablets) { - const tablets = cluster.SystemTablets.slice(0).sort(compareTablets); - info.push({ - label: i18n('tablets'), - value: ( -
- {tablets.map((tablet, tabletIndex) => ( - - ))} -
- ), - }); - } - - if (cluster.Tenants) { - info.push({ - label: i18n('databases'), - value: cluster.Tenants, - }); - } - - info.push( - { - label: i18n('nodes'), - value: , - }, - { - label: i18n('load'), - value: , - }, - { - label: i18n('storage-size'), - value: ( - - ), - }, - ); - - if (Object.keys(groupsStats).length) { - info.push(...getGroupsStatsFields(groupsStats)); - } - - info.push( - ...additionalInfo, - { - label: i18n('links'), - value: ( -
- {links.map(({title, url}) => ( - - ))} -
- ), - }, - { - label: i18n('versions'), - value: , - }, - ); - - return info; -}; - interface ClusterInfoProps { cluster?: TClusterInfo; versionToColor?: VersionToColorMap; @@ -212,20 +34,7 @@ export const ClusterInfo = ({ }: ClusterInfoProps) => { const singleClusterMode = useTypedSelector((state) => state.singleClusterMode); - const {currentData} = nodesApi.useGetNodesQuery({ - tablets: false, - group: 'Version', - }); - - const versionsValues = React.useMemo(() => { - if (!currentData) { - return []; - } - if (Array.isArray(currentData.NodeGroups)) { - return parseNodeGroupsToVersionsValues(currentData.NodeGroups, versionToColor); - } - return parseNodesToVersionsValues(currentData.Nodes, versionToColor); - }, [currentData, versionToColor]); + const versionsValues = useGetVersionValues(cluster, versionToColor); let internalLink = backend + '/internal'; diff --git a/src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.scss b/src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.scss new file mode 100644 index 0000000000..1e7378a206 --- /dev/null +++ b/src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.scss @@ -0,0 +1,11 @@ +.ydb-disk-groups-stats { + display: flex; + flex-direction: column; + gap: var(--g-spacing-3); + &__bar { + cursor: pointer; + } + &__popup-content { + padding: var(--g-spacing-3); + } +} diff --git a/src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.tsx b/src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.tsx new file mode 100644 index 0000000000..d9013dcede --- /dev/null +++ b/src/containers/Cluster/ClusterInfo/components/DiskGroupsStatsBars/DiskGroupsStatsBars.tsx @@ -0,0 +1,78 @@ +import {ContentWithPopup} from '../../../../../components/ContentWithPopup/ContentWithPopup'; +import {InfoViewer} from '../../../../../components/InfoViewer'; +import {ProgressViewer} from '../../../../../components/ProgressViewer/ProgressViewer'; +import type { + DiskErasureGroupsStats, + DiskGroupsStats, +} from '../../../../../store/reducers/cluster/types'; +import {formatBytes, getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers'; +import {cn} from '../../../../../utils/cn'; +import i18n from '../../../i18n'; + +import './DiskGroupsStatsBars.scss'; + +const b = cn('ydb-disk-groups-stats'); + +interface DiskGroupsStatsProps { + stats: DiskGroupsStats; +} + +export const DiskGroupsStatsBars = ({stats}: DiskGroupsStatsProps) => { + return ( +
+ {Object.values(stats).map((erasureStats) => ( + } + > + + + ))} +
+ ); +}; + +interface GroupsStatsPopupContentProps { + stats: DiskErasureGroupsStats; +} + +function GroupsStatsPopupContent({stats}: GroupsStatsPopupContentProps) { + const {diskType, erasure, allocatedSize, availableSize} = stats; + + const sizeToConvert = getSizeWithSignificantDigits(Math.max(allocatedSize, availableSize), 2); + + const convertedAllocatedSize = formatBytes({value: allocatedSize, size: sizeToConvert}); + const convertedAvailableSize = formatBytes({value: availableSize, size: sizeToConvert}); + + const usage = Math.round((allocatedSize / (allocatedSize + availableSize)) * 100); + + const info = [ + { + label: i18n('disk-type'), + value: diskType, + }, + { + label: i18n('erasure'), + value: erasure, + }, + { + label: i18n('allocated'), + value: convertedAllocatedSize, + }, + { + label: i18n('available'), + value: convertedAvailableSize, + }, + { + label: i18n('usage'), + value: usage + '%', + }, + ]; + + return ; +} diff --git a/src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.scss b/src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.scss new file mode 100644 index 0000000000..e32f77726b --- /dev/null +++ b/src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.scss @@ -0,0 +1,16 @@ +@import '../../../../../styles/mixins.scss'; + +.ydb-nodes-state { + display: flex; + justify-content: center; + align-items: center; + + min-width: 30px; + padding: 0 var(--g-spacing-1); + + color: var(--entity-state-font-color); + border: 1px solid var(--entity-state-border-color); + border-radius: var(--g-spacing-1); + background-color: var(--entity-state-background-color); + @include entity-state-colors(); +} diff --git a/src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.tsx b/src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.tsx new file mode 100644 index 0000000000..bd1557e0ca --- /dev/null +++ b/src/containers/Cluster/ClusterInfo/components/NodesState/NodesState.tsx @@ -0,0 +1,15 @@ +import type {EFlag} from '../../../../../types/api/enums'; +import {cn} from '../../../../../utils/cn'; + +import './NodesState.scss'; + +const b = cn('ydb-nodes-state'); + +interface NodesStateProps { + state: EFlag; + children: React.ReactNode; +} + +export function NodesState({state, children}: NodesStateProps) { + return
{children}
; +} diff --git a/src/containers/Cluster/ClusterInfo/shared.ts b/src/containers/Cluster/ClusterInfo/shared.ts new file mode 100644 index 0000000000..9deca58052 --- /dev/null +++ b/src/containers/Cluster/ClusterInfo/shared.ts @@ -0,0 +1,3 @@ +import {cn} from '../../../utils/cn'; + +export const b = cn('cluster-info'); diff --git a/src/containers/Cluster/ClusterInfo/utils.ts b/src/containers/Cluster/ClusterInfo/utils.ts deleted file mode 100644 index 7a34e9f871..0000000000 --- a/src/containers/Cluster/ClusterInfo/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type {TTabletStateInfo} from '../../../types/api/tablet'; -import {EType} from '../../../types/api/tablet'; - -export const compareTablets = (tablet1: TTabletStateInfo, tablet2: TTabletStateInfo) => { - if (tablet1.Type === EType.TxAllocator) { - return 1; - } - - if (tablet2.Type === EType.TxAllocator) { - return -1; - } - - return 0; -}; diff --git a/src/containers/Cluster/ClusterInfo/utils.tsx b/src/containers/Cluster/ClusterInfo/utils.tsx new file mode 100644 index 0000000000..1b135c08dd --- /dev/null +++ b/src/containers/Cluster/ClusterInfo/utils.tsx @@ -0,0 +1,244 @@ +import React from 'react'; + +import {skipToken} from '@reduxjs/toolkit/query'; + +import type {InfoViewerItem} from '../../../components/InfoViewer/InfoViewer'; +import {LinkWithIcon} from '../../../components/LinkWithIcon/LinkWithIcon'; +import {ProgressViewer} from '../../../components/ProgressViewer/ProgressViewer'; +import {Tablet} from '../../../components/Tablet'; +import {Tags} from '../../../components/Tags'; +import type {ClusterGroupsStats} from '../../../store/reducers/cluster/types'; +import {nodesApi} from '../../../store/reducers/nodes/nodes'; +import type {ClusterLink} from '../../../types/additionalProps'; +import {isClusterInfoV2} from '../../../types/api/cluster'; +import type {TClusterInfo} from '../../../types/api/cluster'; +import type {EFlag} from '../../../types/api/enums'; +import type {TTabletStateInfo} from '../../../types/api/tablet'; +import {EType} from '../../../types/api/tablet'; +import type {VersionToColorMap, VersionValue} from '../../../types/versions'; +import {formatStorageValues} from '../../../utils/dataFormatters/dataFormatters'; +import {parseNodeGroupsToVersionsValues, parseNodesToVersionsValues} from '../../../utils/versions'; +import {VersionsBar} from '../VersionsBar/VersionsBar'; +import i18n from '../i18n'; + +import {DiskGroupsStatsBars} from './components/DiskGroupsStatsBars/DiskGroupsStatsBars'; +import {NodesState} from './components/NodesState/NodesState'; +import {b} from './shared'; + +const COLORS_PRIORITY: Record = { + Green: 5, + Blue: 4, + Yellow: 3, + Orange: 2, + Red: 1, + Grey: 0, +}; + +export const compareTablets = (tablet1: TTabletStateInfo, tablet2: TTabletStateInfo) => { + if (tablet1.Type === EType.TxAllocator) { + return 1; + } + + if (tablet2.Type === EType.TxAllocator) { + return -1; + } + + return 0; +}; + +const getGroupsStatsFields = (groupsStats: ClusterGroupsStats) => { + return Object.keys(groupsStats).map((diskType) => { + return { + label: i18n('storage-groups', {diskType}), + value: , + }; + }); +}; + +const getDCInfo = (cluster: TClusterInfo) => { + if (isClusterInfoV2(cluster) && cluster.MapDataCenters) { + return Object.entries(cluster.MapDataCenters).map(([dc, count]) => ( + + {dc}: {i18n('quantity', {count})} + + )); + } + return cluster.DataCenters?.filter(Boolean); +}; + +const getStorageStats = (cluster: TClusterInfo) => { + if (isClusterInfoV2(cluster) && cluster.MapDataCenters) { + const {MapStorageTotal, MapStorageUsed} = cluster; + const storageTypesSet = new Set( + Object.keys(MapStorageTotal ?? []).concat(Object.keys(MapStorageUsed ?? [])), + ); + if (storageTypesSet.size > 0) { + return Array.from(storageTypesSet).reduce( + (acc, storageType) => { + acc[storageType] = { + used: MapStorageUsed?.[storageType], + total: MapStorageTotal?.[storageType], + }; + return acc; + }, + {} as Record, + ); + } + return Object.entries(cluster.MapDataCenters).map(([dc, count]) => ( + + {dc}: {i18n('quantity', {count})} + + )); + } + return {_default: {used: cluster?.StorageUsed, total: cluster?.StorageTotal}}; +}; + +export const getInfo = ( + cluster: TClusterInfo, + versionsValues: VersionValue[], + groupsStats: ClusterGroupsStats, + additionalInfo: InfoViewerItem[], + links: ClusterLink[], +) => { + const info: InfoViewerItem[] = []; + + const dataCenters = getDCInfo(cluster); + + if (dataCenters?.length) { + info.push({ + label: i18n('dc'), + value: , + }); + } + + if (cluster.SystemTablets) { + const tablets = cluster.SystemTablets.slice(0).sort(compareTablets); + info.push({ + label: i18n('tablets'), + value: ( +
+ {tablets.map((tablet, tabletIndex) => ( + + ))} +
+ ), + }); + } + + if (cluster.Tenants) { + info.push({ + label: i18n('databases'), + value: cluster.Tenants, + }); + } + + info.push({ + label: i18n('nodes'), + value: , + }); + + if (isClusterInfoV2(cluster) && cluster.MapNodeStates) { + // const fake = {Grey: 1, Green: 10, Red: 1, Orange: 2, Yellow: 2, Blue: 3}; + // const arrayNodesStates = Object.entries(fake) as [EFlag, number][]; + const arrayNodesStates = Object.entries(cluster.MapNodeStates) as [EFlag, number][]; + // sort stack to achieve order "green, orange, yellow, red, blue, grey" + arrayNodesStates.sort((a, b) => COLORS_PRIORITY[b[0]] - COLORS_PRIORITY[a[0]]); + const nodesStates = arrayNodesStates.map(([state, count]) => { + return ( + + {count} + + ); + }); + info.push({ + label: i18n('nodes-state'), + value:
{nodesStates}
, + }); + } + + info.push({ + label: i18n('load'), + value: , + }); + + const storageStats = getStorageStats(cluster); + + Object.entries(storageStats).forEach(([type, stats]) => { + let label = i18n('storage-size'); + if (type !== '_default') { + label += `, ${type}`; + } + info.push({ + label: label, + value: ( + + ), + }); + }); + + if (Object.keys(groupsStats).length) { + info.push(...getGroupsStatsFields(groupsStats)); + } + + info.push( + ...additionalInfo, + { + label: i18n('links'), + value: ( +
+ {links.map(({title, url}) => ( + + ))} +
+ ), + }, + { + label: i18n('versions'), + value: ( + el.title !== 'unknown')} + /> + ), + }, + ); + + return info; +}; + +export const useGetVersionValues = (cluster?: TClusterInfo, versionToColor?: VersionToColorMap) => { + const {currentData} = nodesApi.useGetNodesQuery( + isClusterInfoV2(cluster) + ? skipToken + : { + tablets: false, + group: 'Version', + }, + ); + + const versionsValues = React.useMemo(() => { + if (isClusterInfoV2(cluster) && cluster.MapVersions) { + const groups = Object.entries(cluster.MapVersions).map(([version, count]) => ({ + name: version, + count, + })); + return parseNodeGroupsToVersionsValues(groups, versionToColor, cluster.NodesTotal); + } + if (!currentData) { + return []; + } + if (Array.isArray(currentData.NodeGroups)) { + return parseNodeGroupsToVersionsValues( + currentData.NodeGroups, + versionToColor, + cluster?.NodesTotal, + ); + } + return parseNodesToVersionsValues(currentData.Nodes, versionToColor); + }, [currentData, versionToColor, cluster]); + + return versionsValues; +}; diff --git a/src/containers/Cluster/i18n/en.json b/src/containers/Cluster/i18n/en.json index 6ec02cd409..cf0eba22d4 100644 --- a/src/containers/Cluster/i18n/en.json +++ b/src/containers/Cluster/i18n/en.json @@ -8,9 +8,15 @@ "tablets": "Tablets", "databases": "Databases", "nodes": "Nodes", + "nodes-state": "Nodes state", "load": "Load", "storage-size": "Storage size", "storage-groups": "Storage groups, {{diskType}}", "links": "Links", - "versions": "Versions" + "versions": "Versions", + "quantity": { + "one": "{{count}} node", + "other": "{{count}} nodes", + "zero": "no nodes" + } } diff --git a/src/containers/Cluster/i18n/index.ts b/src/containers/Cluster/i18n/index.ts index 758b317b3e..483b79634f 100644 --- a/src/containers/Cluster/i18n/index.ts +++ b/src/containers/Cluster/i18n/index.ts @@ -1,8 +1,7 @@ import {registerKeysets} from '../../../utils/i18n'; import en from './en.json'; -import ru from './ru.json'; const COMPONENT = 'ydb-cluster'; -export default registerKeysets(COMPONENT, {ru, en}); +export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Cluster/i18n/ru.json b/src/containers/Cluster/i18n/ru.json deleted file mode 100644 index 8e75e63add..0000000000 --- a/src/containers/Cluster/i18n/ru.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "disk-type": "Тип диска", - "erasure": "Режим", - "allocated": "Использовано", - "available": "Доступно", - "usage": "Потребление", - "dc": "ДЦ", - "tablets": "Таблетки", - "databases": "Базы данных", - "nodes": "Узлы", - "load": "Нагрузка", - "storage-size": "Размер хранилища", - "storage-groups": "Группы хранения, {{diskType}}", - "links": "Ссылки", - "versions": "Версии" -} diff --git a/src/store/reducers/cluster/cluster.ts b/src/store/reducers/cluster/cluster.ts index b5aa65b1ae..14e3597dff 100644 --- a/src/store/reducers/cluster/cluster.ts +++ b/src/store/reducers/cluster/cluster.ts @@ -6,13 +6,18 @@ import {StringParam, useQueryParam} from 'use-query-params'; import type {ClusterTab} from '../../../containers/Cluster/utils'; import {clusterTabsIds, isClusterTab} from '../../../containers/Cluster/utils'; import {parseTraceFields} from '../../../services/parsers/parseMetaCluster'; +import {isClusterInfoV2} from '../../../types/api/cluster'; import type {TClusterInfo} from '../../../types/api/cluster'; import {DEFAULT_CLUSTER_TAB_KEY} from '../../../utils/constants'; import {isQueryErrorResponse} from '../../../utils/query'; import {api} from '../api'; import type {ClusterGroupsStats, ClusterState} from './types'; -import {createSelectClusterGroupsQuery, parseGroupsStatsQueryResponse} from './utils'; +import { + createSelectClusterGroupsQuery, + getGroupStatsFromClusterInfo, + parseGroupsStatsQueryResponse, +} from './utils'; const defaultClusterTabLS = localStorage.getItem(DEFAULT_CLUSTER_TAB_KEY); @@ -67,6 +72,15 @@ export const clusterApi = api.injectEndpoints({ return {data: {clusterData}}; } + if (isClusterInfoV2(clusterData)) { + return { + data: { + clusterData, + groupsStats: getGroupStatsFromClusterInfo(clusterData), + }, + }; + } + try { const query = createSelectClusterGroupsQuery(clusterRoot); diff --git a/src/store/reducers/cluster/utils.ts b/src/store/reducers/cluster/utils.ts index 16c3f7d3fa..2edcc08875 100644 --- a/src/store/reducers/cluster/utils.ts +++ b/src/store/reducers/cluster/utils.ts @@ -1,4 +1,5 @@ -import type {ExecuteQueryResponse} from '../../../types/api/query'; +import type {TClusterInfoV2, TStorageStats} from '../../../types/api/cluster'; +import type {ExecuteQueryResponse, KeyValueRow} from '../../../types/api/query'; import {parseQueryAPIExecuteResponse} from '../../../utils/query'; import type {ClusterGroupsStats} from './types'; @@ -31,13 +32,10 @@ const getDiskType = (rawTypeString: string) => { return diskType; }; -export const parseGroupsStatsQueryResponse = ( - data: ExecuteQueryResponse<'modern'>, -): ClusterGroupsStats => { - const parsedData = parseQueryAPIExecuteResponse(data).result; +function getGroupStats(data?: KeyValueRow[] | TStorageStats[]) { const result: ClusterGroupsStats = {}; - parsedData?.forEach((stats) => { + data?.forEach((stats) => { const { PDiskFilter, ErasureSpecies: erasure, @@ -85,4 +83,15 @@ export const parseGroupsStatsQueryResponse = ( }); return result; +} + +export const parseGroupsStatsQueryResponse = ( + data: ExecuteQueryResponse<'modern'>, +): ClusterGroupsStats => { + const parsedData = parseQueryAPIExecuteResponse(data).result; + return getGroupStats(parsedData); }; + +export function getGroupStatsFromClusterInfo(info: TClusterInfoV2) { + return getGroupStats(info.StorageStats); +} diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index ca142099f7..ba60b8fae2 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -323,3 +323,48 @@ } } } + +@mixin entity-state-colors { + --entity-state-border-color: var(--g-color-base-misc-heavy); + --entity-state-background-color: var(--g-color-base-misc-light); + --entity-state-fill-color: var(--g-color-base-misc-medium); + --entity-state-font-color: var(--g-color-text-primary); + + &_green { + --entity-state-font-color: var(--g-color-text-positive); + --entity-state-border-color: var(--g-color-base-positive-heavy); + --entity-state-background-color: var(--g-color-base-positive-light); + --entity-state-fill-color: var(--g-color-base-positive-medium); + } + + &_blue { + --entity-state-font-color: var(--g-color-text-info); + --entity-state-border-color: var(--g-color-base-info-heavy); + --entity-state-background-color: var(--g-color-base-info-light); + --entity-state-fill-color: var(--g-color-base-info-medium); + } + + &_yellow { + --entity-state-font-color: var(--g-color-text-warning); + --entity-state-border-color: var(--g-color-base-warning-heavy); + --entity-state-background-color: var(--g-color-base-yellow-light); + --entity-state-fill-color: var(--g-color-base-yellow-medium); + } + + &_orange { + --entity-state-font-color: var(--g-color-text-warning-heavy); + --entity-state-border-color: var(--ydb-color-status-orange); + --entity-state-background-color: var(--g-color-base-warning-light); + --entity-state-fill-color: var(--g-color-base-warning-medium); + } + &_red { + --entity-state-font-color: var(--g-color-text-danger); + --entity-state-border-color: var(--g-color-base-danger-heavy); + --entity-state-background-color: var(--g-color-base-danger-light); + --entity-state-fill-color: var(--g-color-base-danger-medium); + } + &__gray { + --entity-state-font-color: var(--g-color-text-secondary); + --entity-state-border-color: var(--g-color-line-generic-hover); + } +} diff --git a/src/types/api/cluster.ts b/src/types/api/cluster.ts index 3c4acb4e27..8fdb6c7938 100644 --- a/src/types/api/cluster.ts +++ b/src/types/api/cluster.ts @@ -7,7 +7,7 @@ import type {TTraceCheck, TTraceView} from './trace'; * * source: https://github.com/ydb-platform/ydb/blob/main/ydb/core/viewer/protos/viewer.proto */ -export interface TClusterInfo { +interface TClusterInfoV1 { Name?: string; Overall?: EFlag; NodesTotal?: number; @@ -42,3 +42,35 @@ export interface TClusterInfo { TraceView?: TTraceView; TraceCheck?: TTraceCheck; } + +export interface TStorageStats { + PDiskFilter?: string; + ErasureSpecies?: string; + CurrentAvailableSize?: string; + CurrentAllocatedSize?: string; + CurrentGroupsCreated?: number; + AvailableGroupsToCreate?: number; +} +export interface TClusterInfoV2 extends TClusterInfoV1 { + MapDataCenters?: { + [key: string]: number; + }; + MapNodeStates?: Partial>; + MapStorageTotal?: { + [key: string]: string; + }; + MapStorageUsed?: { + [key: string]: string; + }; + MapVersions?: { + [key: string]: number; + }; + StorageStats?: TStorageStats[]; + Version?: number; +} + +export type TClusterInfo = TClusterInfoV1 | TClusterInfoV2; + +export function isClusterInfoV2(info?: TClusterInfo): info is TClusterInfoV2 { + return info ? 'Version' in info && info.Version === 2 : false; +} diff --git a/src/types/api/meta.ts b/src/types/api/meta.ts index 8142b78194..e226d636c1 100644 --- a/src/types/api/meta.ts +++ b/src/types/api/meta.ts @@ -44,9 +44,9 @@ export interface MetaGeneralClusterInfo { } // In case of error in viewer /cluster request mvp return error field instead of cluster data -export interface MetaViewerClusterInfo extends TClusterInfo { +export type MetaViewerClusterInfo = TClusterInfo & { error?: string; -} +}; export interface MetaClusterVersion { version: string; diff --git a/src/utils/versions/parseNodesToVersionsValues.ts b/src/utils/versions/parseNodesToVersionsValues.ts index 6e6661d44f..e33eb208d7 100644 --- a/src/utils/versions/parseNodesToVersionsValues.ts +++ b/src/utils/versions/parseNodesToVersionsValues.ts @@ -32,13 +32,15 @@ export const parseNodesToVersionsValues = ( export function parseNodeGroupsToVersionsValues( groups: NodesGroup[], versionsToColor?: VersionToColorMap, + total?: number, ) { + const normalizedTotal = total ?? groups.reduce((acc, group) => acc + group.count, 0); return groups.map((group) => { return { title: group.name, version: group.name, color: versionsToColor?.get(group.name), - value: group.count, + value: (group.count / normalizedTotal) * 100, }; }); } From 4ae94764602cac0452219c963808c184356a63a5 Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Wed, 11 Sep 2024 17:40:04 +0300 Subject: [PATCH 2/6] fix: code review --- src/containers/Cluster/ClusterInfo/utils.tsx | 7 ------- src/styles/mixins.scss | 2 +- src/types/api/cluster.ts | 3 +++ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/containers/Cluster/ClusterInfo/utils.tsx b/src/containers/Cluster/ClusterInfo/utils.tsx index 1b135c08dd..4a7b4b49d2 100644 --- a/src/containers/Cluster/ClusterInfo/utils.tsx +++ b/src/containers/Cluster/ClusterInfo/utils.tsx @@ -84,11 +84,6 @@ const getStorageStats = (cluster: TClusterInfo) => { {} as Record, ); } - return Object.entries(cluster.MapDataCenters).map(([dc, count]) => ( - - {dc}: {i18n('quantity', {count})} - - )); } return {_default: {used: cluster?.StorageUsed, total: cluster?.StorageTotal}}; }; @@ -138,8 +133,6 @@ export const getInfo = ( }); if (isClusterInfoV2(cluster) && cluster.MapNodeStates) { - // const fake = {Grey: 1, Green: 10, Red: 1, Orange: 2, Yellow: 2, Blue: 3}; - // const arrayNodesStates = Object.entries(fake) as [EFlag, number][]; const arrayNodesStates = Object.entries(cluster.MapNodeStates) as [EFlag, number][]; // sort stack to achieve order "green, orange, yellow, red, blue, grey" arrayNodesStates.sort((a, b) => COLORS_PRIORITY[b[0]] - COLORS_PRIORITY[a[0]]); diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index ba60b8fae2..653c1afd05 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -363,7 +363,7 @@ --entity-state-background-color: var(--g-color-base-danger-light); --entity-state-fill-color: var(--g-color-base-danger-medium); } - &__gray { + &__grey { --entity-state-font-color: var(--g-color-text-secondary); --entity-state-border-color: var(--g-color-line-generic-hover); } diff --git a/src/types/api/cluster.ts b/src/types/api/cluster.ts index 8fdb6c7938..8a42a6be79 100644 --- a/src/types/api/cluster.ts +++ b/src/types/api/cluster.ts @@ -47,6 +47,7 @@ export interface TStorageStats { PDiskFilter?: string; ErasureSpecies?: string; CurrentAvailableSize?: string; + /** uint64 */ CurrentAllocatedSize?: string; CurrentGroupsCreated?: number; AvailableGroupsToCreate?: number; @@ -56,9 +57,11 @@ export interface TClusterInfoV2 extends TClusterInfoV1 { [key: string]: number; }; MapNodeStates?: Partial>; + /** key is uint64 */ MapStorageTotal?: { [key: string]: string; }; + /** key is uint64 */ MapStorageUsed?: { [key: string]: string; }; From 017775dae56540b3994f8eae210f06125659bc04 Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Fri, 13 Sep 2024 12:12:34 +0300 Subject: [PATCH 3/6] fix: review --- src/types/api/cluster.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/api/cluster.ts b/src/types/api/cluster.ts index 8a42a6be79..cb4951d820 100644 --- a/src/types/api/cluster.ts +++ b/src/types/api/cluster.ts @@ -57,11 +57,11 @@ export interface TClusterInfoV2 extends TClusterInfoV1 { [key: string]: number; }; MapNodeStates?: Partial>; - /** key is uint64 */ + /** value is uint64 */ MapStorageTotal?: { [key: string]: string; }; - /** key is uint64 */ + /** value is uint64 */ MapStorageUsed?: { [key: string]: string; }; From 40e26d844deb6e5d7b172f579bf136099bfe365a Mon Sep 17 00:00:00 2001 From: Elena Makarova Date: Fri, 13 Sep 2024 13:47:49 +0300 Subject: [PATCH 4/6] fix: typo --- src/types/api/cluster.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/api/cluster.ts b/src/types/api/cluster.ts index cb4951d820..21564fa3aa 100644 --- a/src/types/api/cluster.ts +++ b/src/types/api/cluster.ts @@ -52,6 +52,7 @@ export interface TStorageStats { CurrentGroupsCreated?: number; AvailableGroupsToCreate?: number; } + export interface TClusterInfoV2 extends TClusterInfoV1 { MapDataCenters?: { [key: string]: number; From 318027ea83e2627acd9319010f798d8d9ec3527b Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Fri, 13 Sep 2024 14:59:45 +0200 Subject: [PATCH 5/6] chore: update caniuse-lite --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bc8aac7354..0cbe30f340 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8106,9 +8106,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001582", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001582.tgz", - "integrity": "sha512-vsJG3V5vgfduaQGVxL53uSX/HUzxyr2eA8xCo36OLal7sRcSZbibJtLeh0qja4sFOr/QQGt4opB4tOy+eOgAxg==", + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", "dev": true, "funding": [ { From b2df25ec0c7eee35585d537dc7aebecf9ffaf247 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Fri, 13 Sep 2024 15:09:39 +0200 Subject: [PATCH 6/6] fix: package build --- src/types/api/cluster.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/api/cluster.ts b/src/types/api/cluster.ts index 21564fa3aa..04c53ba7cc 100644 --- a/src/types/api/cluster.ts +++ b/src/types/api/cluster.ts @@ -7,7 +7,7 @@ import type {TTraceCheck, TTraceView} from './trace'; * * source: https://github.com/ydb-platform/ydb/blob/main/ydb/core/viewer/protos/viewer.proto */ -interface TClusterInfoV1 { +export interface TClusterInfoV1 { Name?: string; Overall?: EFlag; NodesTotal?: number;