ydb-embedded-ui 3.2.3 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/InfoViewer/InfoViewer.scss +10 -0
  3. package/dist/components/InfoViewer/InfoViewer.tsx +12 -2
  4. package/dist/components/InfoViewer/formatters/cdcStream.ts +10 -0
  5. package/dist/components/InfoViewer/formatters/index.ts +3 -0
  6. package/dist/components/InfoViewer/formatters/pqGroup.ts +51 -0
  7. package/dist/components/InfoViewer/formatters/schema.ts +1 -29
  8. package/dist/components/InfoViewer/formatters/topicStats.tsx +50 -0
  9. package/dist/components/InfoViewer/schemaInfo/index.ts +0 -2
  10. package/dist/components/InfoViewer/utils.ts +15 -0
  11. package/dist/components/InternalLink/InternalLink.tsx +17 -0
  12. package/dist/components/InternalLink/index.ts +1 -0
  13. package/dist/components/Tablet/Tablet.js +1 -1
  14. package/dist/components/TabletsStatistic/TabletsStatistic.tsx +1 -1
  15. package/dist/components/VerticalBars/VerticalBars.scss +15 -0
  16. package/dist/components/VerticalBars/VerticalBars.tsx +38 -0
  17. package/dist/components/VerticalBars/index.ts +1 -0
  18. package/dist/containers/App/App.js +0 -11
  19. package/dist/containers/App/App.scss +0 -1
  20. package/dist/containers/Cluster/Cluster.tsx +2 -2
  21. package/dist/containers/Nodes/Nodes.scss +5 -1
  22. package/dist/containers/Nodes/Nodes.tsx +196 -0
  23. package/dist/containers/{App → Nodes}/NodesTable.scss +2 -2
  24. package/dist/{utils/getNodesColumns.js → containers/Nodes/getNodesColumns.tsx} +60 -35
  25. package/dist/containers/Nodes/i18n/en.json +3 -0
  26. package/dist/containers/Nodes/i18n/index.ts +11 -0
  27. package/dist/containers/Nodes/i18n/ru.json +3 -0
  28. package/dist/containers/Nodes/index.ts +1 -0
  29. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +14 -20
  30. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +32 -16
  31. package/dist/containers/Storage/DiskStateProgressBar/index.ts +1 -0
  32. package/dist/containers/Storage/{Pdisk/Pdisk.scss → PDisk/PDisk.scss} +15 -4
  33. package/dist/containers/Storage/PDisk/PDisk.tsx +145 -0
  34. package/dist/containers/Storage/PDisk/__tests__/colors.tsx +37 -0
  35. package/dist/containers/Storage/PDisk/index.ts +1 -0
  36. package/dist/containers/Storage/PDiskPopup/PDiskPopup.scss +3 -0
  37. package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +82 -0
  38. package/dist/containers/Storage/PDiskPopup/index.ts +1 -0
  39. package/dist/containers/Storage/Storage.js +1 -1
  40. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +10 -10
  41. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +1 -0
  42. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +7 -4
  43. package/dist/containers/Storage/VDisk/VDisk.scss +7 -0
  44. package/dist/containers/Storage/VDisk/VDisk.tsx +148 -0
  45. package/dist/containers/Storage/VDisk/__tests__/colors.tsx +209 -0
  46. package/dist/containers/Storage/VDisk/index.ts +1 -0
  47. package/dist/containers/Storage/VDiskPopup/VDiskPopup.scss +14 -0
  48. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +134 -0
  49. package/dist/containers/Storage/VDiskPopup/index.ts +1 -0
  50. package/dist/containers/Storage/utils/constants.ts +2 -9
  51. package/dist/containers/TabletsFilters/TabletsFilters.js +10 -6
  52. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +2 -2
  53. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -4
  54. package/dist/containers/Tenant/Diagnostics/OverloadedShards/OverloadedShards.tsx +1 -1
  55. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +69 -0
  56. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/index.ts +1 -0
  57. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +18 -16
  58. package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx +37 -0
  59. package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/index.ts +1 -0
  60. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +30 -0
  61. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +94 -0
  62. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +3 -0
  63. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/index.ts +11 -0
  64. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +3 -0
  65. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/index.ts +1 -0
  66. package/dist/containers/Tenant/Diagnostics/Overview/utils/index.ts +1 -0
  67. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +42 -0
  68. package/dist/containers/Tenant/utils/schema.ts +19 -0
  69. package/dist/containers/UserSettings/UserSettings.scss +9 -0
  70. package/dist/containers/UserSettings/UserSettings.tsx +41 -11
  71. package/dist/services/api.d.ts +8 -1
  72. package/dist/services/api.js +27 -8
  73. package/dist/store/reducers/index.ts +3 -1
  74. package/dist/store/reducers/nodes.ts +148 -14
  75. package/dist/store/reducers/{clusterNodes.js → nodesList.js} +0 -41
  76. package/dist/store/reducers/settings.js +10 -4
  77. package/dist/store/reducers/storage.js +24 -13
  78. package/dist/store/reducers/tenant.js +5 -4
  79. package/dist/store/reducers/tooltip.ts +1 -1
  80. package/dist/store/reducers/topic.ts +52 -0
  81. package/dist/styles/mixins.scss +19 -11
  82. package/dist/types/api/common.ts +5 -0
  83. package/dist/types/api/compute.ts +1 -1
  84. package/dist/types/api/consumer.ts +12 -10
  85. package/dist/types/api/nodes.ts +2 -0
  86. package/dist/types/api/pdisk.ts +1 -0
  87. package/dist/types/api/schema.ts +3 -3
  88. package/dist/types/api/topic.ts +5 -4
  89. package/dist/types/api/vdisk.ts +2 -1
  90. package/dist/types/store/nodes.ts +56 -6
  91. package/dist/types/store/tooltip.ts +1 -1
  92. package/dist/types/store/topic.ts +21 -0
  93. package/dist/utils/constants.ts +5 -1
  94. package/dist/utils/i18n/i18n.ts +10 -2
  95. package/dist/utils/index.js +1 -1
  96. package/dist/utils/timeParsers/__test__/formatDuration.test.ts +50 -0
  97. package/dist/utils/timeParsers/__test__/protobuf.test.ts +74 -0
  98. package/dist/utils/timeParsers/formatDuration.ts +46 -0
  99. package/dist/utils/timeParsers/i18n/en.json +7 -0
  100. package/dist/utils/timeParsers/i18n/index.ts +11 -0
  101. package/dist/utils/timeParsers/i18n/ru.json +7 -0
  102. package/dist/utils/timeParsers/index.ts +2 -0
  103. package/dist/utils/timeParsers/protobuf.ts +36 -0
  104. package/package.json +1 -1
  105. package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +0 -48
  106. package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +0 -30
  107. package/dist/components/InternalLink/InternalLink.js +0 -23
  108. package/dist/containers/Nodes/Nodes.js +0 -214
  109. package/dist/containers/NodesViewer/NodesViewer.js +0 -163
  110. package/dist/containers/NodesViewer/NodesViewer.scss +0 -66
  111. package/dist/containers/Storage/Pdisk/Pdisk.tsx +0 -153
  112. package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +0 -41
  113. package/dist/containers/Storage/Vdisk/Vdisk.js +0 -275
  114. package/dist/containers/Storage/Vdisk/Vdisk.scss +0 -22
  115. package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +0 -163
  116. package/dist/containers/Tenant/Diagnostics/Compute/Compute.js +0 -139
  117. package/dist/containers/Tenant/Diagnostics/Compute/Compute.scss +0 -14
@@ -19,7 +19,7 @@ import {stringifyVdiskId} from '../../../utils';
19
19
  import {getUsage, isFullDonorData} from '../../../utils/storage';
20
20
 
21
21
  import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
22
- import Vdisk from '../Vdisk/Vdisk';
22
+ import {VDisk} from '../VDisk';
23
23
  import {getDegradedSeverity, getUsageSeverity} from '../utils';
24
24
 
25
25
  import i18n from './i18n';
@@ -254,16 +254,16 @@ function StorageGroups({
254
254
 
255
255
  return donors.length > 0 ? (
256
256
  <Stack className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
257
- <Vdisk
258
- {...el}
259
- PoolName={row[TableColumnsIds.PoolName]}
257
+ <VDisk
258
+ data={el}
259
+ poolName={row[TableColumnsIds.PoolName]}
260
260
  nodes={nodes}
261
261
  />
262
262
  {donors.map((donor) => (
263
- <Vdisk
264
- {...donor}
263
+ <VDisk
264
+ data={donor}
265
265
  // donor and acceptor are always in the same group
266
- PoolName={row[TableColumnsIds.PoolName]}
266
+ poolName={row[TableColumnsIds.PoolName]}
267
267
  nodes={nodes}
268
268
  key={stringifyVdiskId(donor.VDiskId)}
269
269
  />
@@ -271,9 +271,9 @@ function StorageGroups({
271
271
  </Stack>
272
272
  ) : (
273
273
  <div className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
274
- <Vdisk
275
- {...el}
276
- PoolName={row[TableColumnsIds.PoolName]}
274
+ <VDisk
275
+ data={el}
276
+ poolName={row[TableColumnsIds.PoolName]}
277
277
  nodes={nodes}
278
278
  />
279
279
  </div>
@@ -4,6 +4,7 @@
4
4
  overflow-x: auto;
5
5
  overflow-y: hidden;
6
6
  justify-content: left;
7
+ align-items: flex-end;
7
8
 
8
9
  width: max-content;
9
10
  }
@@ -8,7 +8,7 @@ import {VisibleEntities} from '../../../store/reducers/storage';
8
8
  import {NodesUptimeFilterValues} from '../../../utils/nodes';
9
9
 
10
10
  import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
11
- import Pdisk from '../Pdisk/Pdisk';
11
+ import {PDisk} from '../PDisk';
12
12
 
13
13
  import i18n from './i18n';
14
14
  import './StorageNodes.scss';
@@ -132,8 +132,8 @@ function StorageNodes({
132
132
  render: ({value, row}) => (
133
133
  <div className={b('pdisks-wrapper')}>
134
134
  {_.map(value as any, (el) => (
135
- <div className={b('pdisks-item')}>
136
- <Pdisk key={el.PDiskId} {...el} NodeId={row.NodeId} />
135
+ <div className={b('pdisks-item')} key={el.PDiskId}>
136
+ <PDisk data={el} nodeId={row.NodeId} />
137
137
  </div>
138
138
  ))}
139
139
  </div>
@@ -183,7 +183,10 @@ function StorageNodes({
183
183
  theme="yandex-cloud"
184
184
  data={data}
185
185
  columns={columns}
186
- settings={tableSettings}
186
+ settings={{
187
+ ...tableSettings,
188
+ dynamicRenderType: 'variable',
189
+ }}
187
190
  initialSortOrder={setSortOrder(visibleEntities)}
188
191
  emptyDataMessage={i18n('empty.default')}
189
192
  />
@@ -0,0 +1,7 @@
1
+ .vdisk-storage {
2
+ border-radius: 4px; // to match interactive area with disk shape
3
+
4
+ &__content {
5
+ border-radius: 4px; // to match interactive area with disk shape
6
+ }
7
+ }
@@ -0,0 +1,148 @@
1
+ import React, {useEffect, useState, useRef, useMemo} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+
4
+ import {InternalLink} from '../../../components/InternalLink';
5
+
6
+ import routes, {createHref} from '../../../routes';
7
+ import {EFlag} from '../../../types/api/enums';
8
+ import {EVDiskState, TVDiskStateInfo} from '../../../types/api/vdisk';
9
+ import {stringifyVdiskId} from '../../../utils';
10
+
11
+ import {STRUCTURE} from '../../Node/NodePages';
12
+
13
+ import {DiskStateProgressBar, EDiskStateSeverity} from '../DiskStateProgressBar';
14
+ import type {NodesHosts} from '../PDiskPopup';
15
+ import {VDiskPopup} from '../VDiskPopup';
16
+
17
+ import {NOT_AVAILABLE_SEVERITY} from '../utils';
18
+
19
+ import './VDisk.scss';
20
+
21
+ const b = cn('vdisk-storage');
22
+
23
+ const stateSeverity: Record<EVDiskState, EDiskStateSeverity> = {
24
+ Initial: EDiskStateSeverity.Yellow,
25
+ LocalRecoveryError: EDiskStateSeverity.Red,
26
+ SyncGuidRecoveryError: EDiskStateSeverity.Red,
27
+ SyncGuidRecovery: EDiskStateSeverity.Yellow,
28
+ PDiskError: EDiskStateSeverity.Red,
29
+ OK: EDiskStateSeverity.Green,
30
+ };
31
+
32
+ const getStateSeverity = (vDiskState?: EVDiskState) => {
33
+ if (!vDiskState) {
34
+ return NOT_AVAILABLE_SEVERITY;
35
+ }
36
+
37
+ return stateSeverity[vDiskState] ?? NOT_AVAILABLE_SEVERITY;
38
+ };
39
+
40
+ const getColorSeverity = (color?: EFlag) => {
41
+ if (!color) {
42
+ return EDiskStateSeverity.Grey;
43
+ }
44
+
45
+ return EDiskStateSeverity[color] ?? EDiskStateSeverity.Grey;
46
+ };
47
+
48
+ interface VDiskProps {
49
+ data?: TVDiskStateInfo;
50
+ poolName?: string;
51
+ nodes?: NodesHosts;
52
+ compact?: boolean;
53
+ }
54
+
55
+ export const VDisk = ({data = {}, poolName, nodes, compact}: VDiskProps) => {
56
+ const [severity, setSeverity] = useState(getStateSeverity(data.VDiskState));
57
+ const [isPopupVisible, setIsPopupVisible] = useState(false);
58
+
59
+ const anchor = useRef(null);
60
+
61
+ // determine disk status severity
62
+ useEffect(() => {
63
+ const {DiskSpace, VDiskState, FrontQueues, Replicated, DonorMode} = data;
64
+
65
+ // if the disk is not available, this determines its status severity regardless of other features
66
+ if (!VDiskState) {
67
+ setSeverity(NOT_AVAILABLE_SEVERITY);
68
+ return;
69
+ }
70
+
71
+ const DiskSpaceSeverity = getColorSeverity(DiskSpace);
72
+ const VDiskSpaceSeverity = getStateSeverity(VDiskState);
73
+ const FrontQueuesSeverity = Math.min(
74
+ EDiskStateSeverity.Orange,
75
+ getColorSeverity(FrontQueues),
76
+ );
77
+
78
+ let newSeverity = Math.max(DiskSpaceSeverity, VDiskSpaceSeverity, FrontQueuesSeverity);
79
+
80
+ // donors are always in the not replicated state since they are leftovers
81
+ // painting them blue is useless
82
+ if (!Replicated && !DonorMode && newSeverity === EDiskStateSeverity.Green) {
83
+ newSeverity = EDiskStateSeverity.Blue;
84
+ }
85
+
86
+ setSeverity(newSeverity);
87
+ }, [data]);
88
+
89
+ const showPopup = () => {
90
+ setIsPopupVisible(true);
91
+ };
92
+
93
+ const hidePopup = () => {
94
+ setIsPopupVisible(false);
95
+ };
96
+
97
+ const vdiskAllocatedPercent = useMemo(() => {
98
+ const {AvailableSize, AllocatedSize, PDisk} = data;
99
+ const available = AvailableSize ? AvailableSize : PDisk?.AvailableSize;
100
+
101
+ if (!available) {
102
+ return undefined;
103
+ }
104
+
105
+ return isNaN(Number(AllocatedSize))
106
+ ? undefined
107
+ : (Number(AllocatedSize) * 100) / (Number(available) + Number(AllocatedSize));
108
+ }, [data]);
109
+
110
+ return (
111
+ <React.Fragment>
112
+ <VDiskPopup
113
+ data={data}
114
+ poolName={poolName}
115
+ nodes={nodes}
116
+ anchorRef={anchor}
117
+ open={isPopupVisible}
118
+ />
119
+ <div className={b()} ref={anchor} onMouseEnter={showPopup} onMouseLeave={hidePopup}>
120
+ {data.NodeId ? (
121
+ <InternalLink
122
+ to={createHref(
123
+ routes.node,
124
+ {id: data.NodeId, activeTab: STRUCTURE},
125
+ {
126
+ pdiskId: data.PDisk?.PDiskId,
127
+ vdiskId: stringifyVdiskId(data.VDiskId),
128
+ },
129
+ )}
130
+ className={b('content')}
131
+ >
132
+ <DiskStateProgressBar
133
+ diskAllocatedPercent={vdiskAllocatedPercent}
134
+ severity={severity}
135
+ compact={compact}
136
+ />
137
+ </InternalLink>
138
+ ) : (
139
+ <DiskStateProgressBar
140
+ diskAllocatedPercent={vdiskAllocatedPercent}
141
+ severity={severity}
142
+ compact={compact}
143
+ />
144
+ )}
145
+ </div>
146
+ </React.Fragment>
147
+ );
148
+ };
@@ -0,0 +1,209 @@
1
+ import {renderWithStore} from '../../../../utils/tests/providers';
2
+
3
+ import {EVDiskState} from '../../../../types/api/vdisk';
4
+ import {EFlag} from '../../../../types/api/enums';
5
+
6
+ import {VDisk} from '../VDisk';
7
+
8
+ describe('VDisk state', () => {
9
+ it('Should determine severity based on the highest value among VDiskState, DiskSpace and FrontQueues', () => {
10
+ const {getAllByRole} = renderWithStore(
11
+ <>
12
+ <VDisk
13
+ data={{
14
+ VDiskId: {Domain: 1},
15
+ VDiskState: EVDiskState.OK, // severity 1, green
16
+ DiskSpace: EFlag.Yellow, // severity 3, yellow
17
+ FrontQueues: EFlag.Green, // severity 1, green
18
+ }}
19
+ />
20
+ <VDisk
21
+ data={{
22
+ VDiskId: {Domain: 2},
23
+ VDiskState: EVDiskState.PDiskError, // severity 5, red
24
+ DiskSpace: EFlag.Yellow, // severity 3, yellow
25
+ FrontQueues: EFlag.Green, // severity 1, green
26
+ }}
27
+ />
28
+ <VDisk
29
+ data={{
30
+ VDiskId: {Domain: 3},
31
+ VDiskState: EVDiskState.OK, // severity 1, green
32
+ DiskSpace: EFlag.Yellow, // severity 3, yellow
33
+ FrontQueues: EFlag.Orange, // severity 4, orange
34
+ }}
35
+ />
36
+ </>,
37
+ );
38
+
39
+ const [disk1, disk2, disk3] = getAllByRole('meter');
40
+
41
+ expect(disk1.className).toMatch(/_yellow\b/i);
42
+ expect(disk2.className).toMatch(/_red\b/i);
43
+ expect(disk3.className).toMatch(/_orange\b/i);
44
+ });
45
+
46
+ it('Should not pick the highest severity based on FrontQueues value', () => {
47
+ const {getAllByRole} = renderWithStore(
48
+ <>
49
+ <VDisk
50
+ data={{
51
+ VDiskId: {Domain: 1},
52
+ VDiskState: EVDiskState.OK, // severity 1, green
53
+ DiskSpace: EFlag.Green, // severity 1, green
54
+ FrontQueues: EFlag.Red, // severity 5, red
55
+ }}
56
+ />
57
+ <VDisk
58
+ data={{
59
+ VDiskId: {Domain: 2},
60
+ VDiskState: EVDiskState.OK, // severity 1, green
61
+ DiskSpace: EFlag.Red, // severity 5, red
62
+ FrontQueues: EFlag.Red, // severity 5, red
63
+ }}
64
+ />
65
+ </>,
66
+ );
67
+
68
+ const [disk1, disk2] = getAllByRole('meter');
69
+
70
+ expect(disk1.className).not.toMatch(/_red\b/i);
71
+ expect(disk2.className).toMatch(/_red\b/i);
72
+ });
73
+
74
+ // prettier-ignore
75
+ it('Should display as unavailable when no VDiskState is provided', () => {
76
+ const {getAllByRole} = renderWithStore(
77
+ <>
78
+ <VDisk data={{VDiskId: {Domain: 1}}} />
79
+ <VDisk data={{VDiskId: {Domain: 2}, VDiskState: EVDiskState.OK}} />
80
+ <VDisk data={{VDiskId: {Domain: 3}, DiskSpace: EFlag.Green}} />
81
+ <VDisk data={{VDiskId: {Domain: 4}, FrontQueues: EFlag.Green}} />
82
+ <VDisk data={{VDiskId: {Domain: 5}, VDiskState: EVDiskState.OK, DiskSpace: EFlag.Green}} />
83
+ <VDisk data={{VDiskId: {Domain: 6}, VDiskState: EVDiskState.OK, FrontQueues: EFlag.Green}} />
84
+ <VDisk data={{VDiskId: {Domain: 7}, DiskSpace: EFlag.Green, FrontQueues: EFlag.Green}} />
85
+ <VDisk data={{VDiskId: {Domain: 8}, VDiskState: EVDiskState.OK, DiskSpace: EFlag.Green, FrontQueues: EFlag.Green}} />
86
+ </>
87
+ );
88
+
89
+ const [disk1, disk2, disk3, disk4, disk5, disk6, disk7, disk8] =
90
+ getAllByRole('meter');
91
+
92
+ // unavailable disks display with the grey color
93
+ expect(disk1.className).toMatch(/_grey\b/i);
94
+ expect(disk2.className).not.toMatch(/_grey\b/i);
95
+ expect(disk3.className).toMatch(/_grey\b/i);
96
+ expect(disk4.className).toMatch(/_grey\b/i);
97
+ expect(disk5.className).not.toMatch(/_grey\b/i);
98
+ expect(disk6.className).not.toMatch(/_grey\b/i);
99
+ expect(disk7.className).toMatch(/_grey\b/i);
100
+ expect(disk8.className).not.toMatch(/_grey\b/i);
101
+ });
102
+
103
+ it('Should display as unavailable when no VDiskState is provided even if DiskSpace or FrontQueues flags are not green', () => {
104
+ const {getByRole} = renderWithStore(
105
+ <VDisk
106
+ data={{
107
+ VDiskId: {Domain: 1},
108
+ DiskSpace: EFlag.Red,
109
+ FrontQueues: EFlag.Yellow,
110
+ }}
111
+ />,
112
+ );
113
+
114
+ const disk = getByRole('meter');
115
+
116
+ // unavailable disks display with the grey color
117
+ expect(disk.className).toMatch(/_grey\b/i);
118
+ });
119
+
120
+ it('Should display replicating VDisks in OK state with a distinct color', () => {
121
+ const {getAllByRole} = renderWithStore(
122
+ <>
123
+ <VDisk
124
+ data={{
125
+ VDiskId: {Domain: 1},
126
+ VDiskState: EVDiskState.OK, // severity 1, green
127
+ Replicated: false,
128
+ }}
129
+ />
130
+ <VDisk
131
+ data={{
132
+ VDiskId: {Domain: 2},
133
+ VDiskState: EVDiskState.OK, // severity 1, green
134
+ Replicated: true,
135
+ }}
136
+ />
137
+ </>,
138
+ );
139
+
140
+ const [disk1, disk2] = getAllByRole('meter');
141
+
142
+ expect(disk1.className).toMatch(/_blue\b/i);
143
+ expect(disk2.className).not.toMatch(/_blue\b/i);
144
+ });
145
+
146
+ it('Should display replicating VDisks in a not-OK state with a regular color', () => {
147
+ const {getAllByRole} = renderWithStore(
148
+ <>
149
+ <VDisk
150
+ data={{
151
+ VDiskId: {Domain: 1},
152
+ VDiskState: EVDiskState.Initial, // severity 3, yellow
153
+ Replicated: false,
154
+ }}
155
+ />
156
+ <VDisk
157
+ data={{
158
+ VDiskId: {Domain: 2},
159
+ VDiskState: EVDiskState.PDiskError, // severity 5, red
160
+ Replicated: false,
161
+ }}
162
+ />
163
+ </>,
164
+ );
165
+
166
+ const [disk1, disk2] = getAllByRole('meter');
167
+
168
+ expect(disk1.className).toMatch(/_yellow\b/i);
169
+ expect(disk2.className).toMatch(/_red\b/i);
170
+ });
171
+
172
+ it('Should always display donor VDisks with a regular color', () => {
173
+ const {getAllByRole} = renderWithStore(
174
+ <>
175
+ <VDisk
176
+ data={{
177
+ VDiskId: {Domain: 1},
178
+ VDiskState: EVDiskState.OK, // severity 1, green
179
+ Replicated: false, // donors are always in the not replicated state since they are leftovers
180
+ DonorMode: true,
181
+ }}
182
+ />
183
+ <VDisk
184
+ data={{
185
+ VDiskId: {Domain: 2},
186
+ VDiskState: EVDiskState.Initial, // severity 3, yellow
187
+ Replicated: false,
188
+ DonorMode: true,
189
+ }}
190
+ />
191
+ <VDisk
192
+ data={{
193
+ VDiskId: {Domain: 3},
194
+ VDiskState: EVDiskState.PDiskError, // severity 5, red
195
+ Replicated: false,
196
+ DonorMode: true,
197
+ }}
198
+ />
199
+ </>,
200
+ );
201
+
202
+ const [disk1, disk2, disk3] = getAllByRole('meter');
203
+
204
+ expect(disk1.className).not.toMatch(/_blue\b/i);
205
+ expect(disk1.className).toMatch(/_green\b/i);
206
+ expect(disk2.className).toMatch(/_yellow\b/i);
207
+ expect(disk3.className).toMatch(/_red\b/i);
208
+ });
209
+ });
@@ -0,0 +1 @@
1
+ export * from './VDisk';
@@ -0,0 +1,14 @@
1
+ .vdisk-storage-popup {
2
+ padding: 12px;
3
+
4
+ .info-viewer + .info-viewer {
5
+ margin-top: 8px;
6
+ padding-top: 8px;
7
+
8
+ border-top: 1px solid var(--yc-color-line-generic);
9
+ }
10
+
11
+ &__donor-label {
12
+ margin-bottom: 8px;
13
+ }
14
+ }
@@ -0,0 +1,134 @@
1
+ import {useMemo} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+
4
+ import {Label, Popup, PopupProps} from '@gravity-ui/uikit';
5
+
6
+ import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer';
7
+
8
+ import {EFlag} from '../../../types/api/enums';
9
+ import {TVDiskStateInfo} from '../../../types/api/vdisk';
10
+ import {stringifyVdiskId} from '../../../utils';
11
+ import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
12
+
13
+ import {NodesHosts, preparePDiskData} from '../PDiskPopup';
14
+
15
+ import './VDiskPopup.scss';
16
+
17
+ const b = cn('vdisk-storage-popup');
18
+
19
+ const prepareVDiskData = (data: TVDiskStateInfo, poolName?: string) => {
20
+ const {
21
+ VDiskId,
22
+ VDiskState,
23
+ SatisfactionRank,
24
+ DiskSpace,
25
+ FrontQueues,
26
+ Replicated,
27
+ UnsyncedVDisks,
28
+ AllocatedSize,
29
+ ReadThroughput,
30
+ WriteThroughput,
31
+ } = data;
32
+
33
+ const vdiskData: InfoViewerItem[] = [
34
+ {label: 'VDisk', value: stringifyVdiskId(VDiskId)},
35
+ {label: 'State', value: VDiskState ?? 'not available'},
36
+ ];
37
+
38
+ if (poolName) {
39
+ vdiskData.push({label: 'StoragePool', value: poolName});
40
+ }
41
+
42
+ if (SatisfactionRank && SatisfactionRank.FreshRank?.Flag !== EFlag.Green) {
43
+ vdiskData.push({
44
+ label: 'Fresh',
45
+ value: SatisfactionRank.FreshRank?.Flag,
46
+ });
47
+ }
48
+
49
+ if (SatisfactionRank && SatisfactionRank.LevelRank?.Flag !== EFlag.Green) {
50
+ vdiskData.push({
51
+ label: 'Level',
52
+ value: SatisfactionRank.LevelRank?.Flag,
53
+ });
54
+ }
55
+
56
+ if (SatisfactionRank && SatisfactionRank.FreshRank?.RankPercent) {
57
+ vdiskData.push({
58
+ label: 'Fresh',
59
+ value: SatisfactionRank.FreshRank.RankPercent,
60
+ });
61
+ }
62
+
63
+ if (SatisfactionRank && SatisfactionRank.LevelRank?.RankPercent) {
64
+ vdiskData.push({
65
+ label: 'Level',
66
+ value: SatisfactionRank.LevelRank.RankPercent,
67
+ });
68
+ }
69
+
70
+ if (DiskSpace && DiskSpace !== EFlag.Green) {
71
+ vdiskData.push({label: 'Space', value: DiskSpace});
72
+ }
73
+
74
+ if (FrontQueues && FrontQueues !== EFlag.Green) {
75
+ vdiskData.push({label: 'FrontQueues', value: FrontQueues});
76
+ }
77
+
78
+ if (!Replicated) {
79
+ vdiskData.push({label: 'Replicated', value: 'NO'});
80
+ }
81
+
82
+ if (UnsyncedVDisks) {
83
+ vdiskData.push({label: 'UnsyncVDisks', value: UnsyncedVDisks});
84
+ }
85
+
86
+ if (Number(AllocatedSize)) {
87
+ vdiskData.push({
88
+ label: 'Allocated',
89
+ value: bytesToGB(AllocatedSize),
90
+ });
91
+ }
92
+
93
+ if (Number(ReadThroughput)) {
94
+ vdiskData.push({label: 'Read', value: bytesToSpeed(ReadThroughput)});
95
+ }
96
+
97
+ if (Number(WriteThroughput)) {
98
+ vdiskData.push({
99
+ label: 'Write',
100
+ value: bytesToSpeed(WriteThroughput),
101
+ });
102
+ }
103
+
104
+ return vdiskData;
105
+ };
106
+
107
+ interface VDiskPopupProps extends PopupProps {
108
+ data: TVDiskStateInfo;
109
+ poolName?: string;
110
+ nodes?: NodesHosts;
111
+ }
112
+
113
+ export const VDiskPopup = ({data, poolName, nodes, ...props}: VDiskPopupProps) => {
114
+ const vdiskInfo = useMemo(() => prepareVDiskData(data, poolName), [data, poolName]);
115
+ const pdiskInfo = useMemo(
116
+ () => data.PDisk && preparePDiskData(data.PDisk, nodes),
117
+ [data.PDisk, nodes],
118
+ );
119
+
120
+ return (
121
+ <Popup
122
+ className={b()}
123
+ placement={['top', 'bottom']}
124
+ // bigger offset for easier switching to neighbour nodes
125
+ // matches the default offset for popup with arrow out of a sense of beauty
126
+ offset={[0, 12]}
127
+ {...props}
128
+ >
129
+ {data.DonorMode && <Label className={b('donor-label')}>Donor</Label>}
130
+ <InfoViewer title="VDisk" info={vdiskInfo} size="s" />
131
+ {pdiskInfo && <InfoViewer title="PDisk" info={pdiskInfo} size="s" />}
132
+ </Popup>
133
+ );
134
+ };
@@ -0,0 +1 @@
1
+ export * from './VDiskPopup';
@@ -1,10 +1,3 @@
1
- export const colorSeverity = {
2
- Grey: 0,
3
- Green: 1,
4
- Blue: 2,
5
- Yellow: 3,
6
- Orange: 4,
7
- Red: 5,
8
- };
1
+ import {EDiskStateSeverity} from '../DiskStateProgressBar';
9
2
 
10
- export const NOT_AVAILABLE_SEVERITY = colorSeverity.Red;
3
+ export const NOT_AVAILABLE_SEVERITY = EDiskStateSeverity.Grey;
@@ -173,11 +173,13 @@ class TabletsFilters extends React.Component {
173
173
 
174
174
  return (
175
175
  <div className={b()}>
176
- {/* {this.renderOverall(tablets)} */}
177
-
178
- <div className={b('tenant')}>
179
- <span className={b('label')}>Database: </span> {tenantPath}
180
- </div>
176
+ {tenantPath ? (
177
+ <div className={b('tenant')}>
178
+ <>
179
+ <span className={b('label')}>Database: </span> {tenantPath}
180
+ </>
181
+ </div>
182
+ ) : null}
181
183
  <MemoizedFilters
182
184
  nodesForSelect={nodesForSelect}
183
185
  nodeFilter={nodeFilter}
@@ -253,7 +255,9 @@ const Filters = ({
253
255
  return (
254
256
  <div className={b('node')}>
255
257
  <div>{option.content}</div>
256
- <div className={b('node-meta')} title={option.meta}>{option.meta}</div>
258
+ <div className={b('node-meta')} title={option.meta}>
259
+ {option.meta}
260
+ </div>
257
261
  </div>
258
262
  );
259
263
  }}
@@ -39,8 +39,8 @@
39
39
  width: 100%;
40
40
  padding: 0 20px;
41
41
 
42
- & .nodes-viewer,
43
- & .global-storage {
42
+ & .global-storage,
43
+ & .ydb-nodes {
44
44
  &__controls {
45
45
  padding-top: 0;
46
46
  }