ydb-embedded-ui 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) 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/Consumers/Consumers.tsx +2 -1
  53. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +2 -2
  54. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -4
  55. package/dist/containers/Tenant/Diagnostics/OverloadedShards/OverloadedShards.tsx +1 -1
  56. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +69 -0
  57. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/index.ts +1 -0
  58. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +18 -16
  59. package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx +37 -0
  60. package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/index.ts +1 -0
  61. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +30 -0
  62. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +94 -0
  63. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +3 -0
  64. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/index.ts +11 -0
  65. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +3 -0
  66. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/index.ts +1 -0
  67. package/dist/containers/Tenant/Diagnostics/Overview/utils/index.ts +1 -0
  68. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +42 -0
  69. package/dist/containers/Tenant/utils/schema.ts +19 -0
  70. package/dist/containers/Tenants/Tenants.js +2 -1
  71. package/dist/containers/UserSettings/UserSettings.tsx +18 -10
  72. package/dist/services/api.d.ts +8 -1
  73. package/dist/services/api.js +27 -8
  74. package/dist/store/reducers/index.ts +3 -1
  75. package/dist/store/reducers/nodes.ts +148 -14
  76. package/dist/store/reducers/{clusterNodes.js → nodesList.js} +0 -41
  77. package/dist/store/reducers/settings.js +10 -4
  78. package/dist/store/reducers/storage.js +24 -13
  79. package/dist/store/reducers/tenant.js +5 -4
  80. package/dist/store/reducers/tooltip.ts +1 -1
  81. package/dist/store/reducers/topic.ts +52 -0
  82. package/dist/styles/mixins.scss +19 -11
  83. package/dist/types/api/common.ts +5 -0
  84. package/dist/types/api/compute.ts +1 -1
  85. package/dist/types/api/consumer.ts +12 -10
  86. package/dist/types/api/nodes.ts +2 -0
  87. package/dist/types/api/pdisk.ts +1 -0
  88. package/dist/types/api/schema.ts +3 -3
  89. package/dist/types/api/topic.ts +5 -4
  90. package/dist/types/api/vdisk.ts +2 -1
  91. package/dist/types/store/nodes.ts +56 -6
  92. package/dist/types/store/tooltip.ts +1 -1
  93. package/dist/types/store/topic.ts +21 -0
  94. package/dist/utils/constants.ts +5 -1
  95. package/dist/utils/i18n/i18n.ts +10 -2
  96. package/dist/utils/index.js +1 -1
  97. package/dist/utils/timeParsers/__test__/formatDuration.test.ts +50 -0
  98. package/dist/utils/timeParsers/__test__/protobuf.test.ts +74 -0
  99. package/dist/utils/timeParsers/formatDuration.ts +46 -0
  100. package/dist/utils/timeParsers/i18n/en.json +7 -0
  101. package/dist/utils/timeParsers/i18n/index.ts +11 -0
  102. package/dist/utils/timeParsers/i18n/ru.json +7 -0
  103. package/dist/utils/timeParsers/index.ts +2 -0
  104. package/dist/utils/timeParsers/protobuf.ts +36 -0
  105. package/package.json +1 -1
  106. package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +0 -48
  107. package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +0 -30
  108. package/dist/components/InternalLink/InternalLink.js +0 -23
  109. package/dist/containers/Nodes/Nodes.js +0 -213
  110. package/dist/containers/NodesViewer/NodesViewer.js +0 -163
  111. package/dist/containers/NodesViewer/NodesViewer.scss +0 -66
  112. package/dist/containers/Storage/Pdisk/Pdisk.tsx +0 -153
  113. package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +0 -41
  114. package/dist/containers/Storage/Vdisk/Vdisk.js +0 -275
  115. package/dist/containers/Storage/Vdisk/Vdisk.scss +0 -22
  116. package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +0 -163
  117. package/dist/containers/Tenant/Diagnostics/Compute/Compute.js +0 -139
  118. 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
  }}
@@ -1,6 +1,7 @@
1
1
  import {useCallback, useEffect, useState} from 'react';
2
2
  import {useDispatch} from 'react-redux';
3
3
  import block from 'bem-cn-lite';
4
+ import { escapeRegExp } from 'lodash/fp';
4
5
 
5
6
  import DataTable, {Column} from '@yandex-cloud/react-data-table';
6
7
 
@@ -73,7 +74,7 @@ export const Consumers = ({path, type}: ConsumersProps) => {
73
74
  const filterConsumersByName = (search: string) => {
74
75
  const filteredConsumers = search
75
76
  ? consumers.filter((consumer) => {
76
- const re = new RegExp(search, 'i');
77
+ const re = new RegExp(escapeRegExp(search), 'i');
77
78
  return re.test(consumer.name);
78
79
  })
79
80
  : consumers;
@@ -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
  }