ydb-embedded-ui 3.3.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/components/Errors/ResponseError/ResponseError.tsx +2 -2
  3. package/dist/components/InfoViewer/formatters/topicStats.tsx +8 -29
  4. package/dist/components/LabelWithPopover/LabelWithPopover.tsx +20 -0
  5. package/dist/components/LabelWithPopover/index.ts +1 -0
  6. package/dist/components/LagImages/LagImages.tsx +205 -0
  7. package/dist/components/LagImages/index.ts +1 -0
  8. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.scss +92 -0
  9. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +120 -0
  10. package/dist/components/SpeedMultiMeter/i18n/en.json +6 -0
  11. package/dist/components/SpeedMultiMeter/i18n/index.ts +13 -0
  12. package/dist/components/SpeedMultiMeter/i18n/ru.json +6 -0
  13. package/dist/components/SpeedMultiMeter/index.ts +1 -0
  14. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +18 -14
  15. package/dist/containers/Storage/VDisk/VDisk.tsx +20 -5
  16. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +34 -5
  17. package/dist/containers/Storage/utils/types.ts +5 -0
  18. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.scss +32 -3
  19. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +62 -69
  20. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +13 -0
  21. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +27 -0
  22. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/index.ts +1 -0
  23. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.scss +32 -0
  24. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.tsx +43 -0
  25. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/index.ts +1 -0
  26. package/dist/containers/Tenant/Diagnostics/Consumers/columns/Columns.scss +5 -0
  27. package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +66 -0
  28. package/dist/containers/Tenant/Diagnostics/Consumers/columns/index.ts +1 -0
  29. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/en.json +4 -3
  30. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +4 -3
  31. package/dist/containers/Tenant/Diagnostics/Consumers/utils/constants.ts +23 -0
  32. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -0
  33. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +8 -2
  34. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +9 -1
  35. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +6 -8
  36. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +33 -0
  37. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +76 -0
  38. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/index.ts +1 -0
  39. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +45 -0
  40. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +254 -0
  41. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsWrapper.tsx +79 -0
  42. package/dist/containers/Tenant/Diagnostics/Partitions/columns/Columns.scss +13 -0
  43. package/dist/containers/Tenant/Diagnostics/Partitions/columns/columns.tsx +246 -0
  44. package/dist/containers/Tenant/Diagnostics/Partitions/columns/index.ts +1 -0
  45. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +13 -0
  46. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/index.ts +11 -0
  47. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +13 -0
  48. package/dist/containers/Tenant/Diagnostics/Partitions/index.ts +1 -0
  49. package/dist/containers/Tenant/Diagnostics/Partitions/utils/constants.ts +74 -0
  50. package/dist/containers/Tenant/Diagnostics/Partitions/utils/types.ts +6 -0
  51. package/dist/containers/Tenant/utils/schema.ts +1 -16
  52. package/dist/services/api.d.ts +4 -0
  53. package/dist/services/api.js +22 -6
  54. package/dist/store/reducers/consumer.ts +160 -0
  55. package/dist/store/reducers/index.ts +2 -0
  56. package/dist/store/reducers/settings.js +2 -0
  57. package/dist/store/reducers/topic.ts +82 -2
  58. package/dist/types/store/consumer.ts +55 -0
  59. package/dist/types/store/topic.ts +23 -6
  60. package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +24 -0
  61. package/dist/utils/bytesParsers/formatBytesCustom.ts +57 -0
  62. package/dist/utils/bytesParsers/i18n/en.json +7 -0
  63. package/dist/utils/bytesParsers/i18n/index.ts +11 -0
  64. package/dist/utils/bytesParsers/i18n/ru.json +7 -0
  65. package/dist/utils/bytesParsers/index.ts +2 -0
  66. package/dist/utils/constants.ts +3 -0
  67. package/dist/utils/index.js +6 -0
  68. package/dist/utils/storage.ts +2 -2
  69. package/dist/utils/timeParsers/index.ts +2 -1
  70. package/dist/utils/timeParsers/parsers.ts +18 -0
  71. package/dist/utils/timeParsers/{protobuf.ts → protobufParsers.ts} +0 -0
  72. package/dist/utils/utils.js +3 -3
  73. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.4.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.3.4...v3.4.0) (2023-02-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * **Diagnostics:** add Partitions tab ([914702b](https://github.com/ydb-platform/ydb-embedded-ui/commit/914702be7e8aea28fcdc9f2ddf1cb7356995146a))
9
+ * **Diagnostics:** rework Consumers tab ([0dae9d8](https://github.com/ydb-platform/ydb-embedded-ui/commit/0dae9d84c254d556db2a0d18345fdc10c152172a))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * add read and lag images ([a3f0648](https://github.com/ydb-platform/ydb-embedded-ui/commit/a3f0648fc4f23c2ac2c9e73c4078bf5f06d1a57e))
15
+ * add reducer for consumer ([4ab65e3](https://github.com/ydb-platform/ydb-embedded-ui/commit/4ab65e3fb3dd4f29b4757473275ba84bec0f5411))
16
+ * add SpeedMultiMeter component ([39acbf1](https://github.com/ydb-platform/ydb-embedded-ui/commit/39acbf1a1e234f36a090b29935872e694e1525c0))
17
+ * **ResponseError:** make error prop optional ([f706e94](https://github.com/ydb-platform/ydb-embedded-ui/commit/f706e940e51e62841e18338775b01183831761e1))
18
+ * **Storage:** display not full donors ([13f4b9f](https://github.com/ydb-platform/ydb-embedded-ui/commit/13f4b9fe9f796e8ef6fee094f7b5bc6056e2833b))
19
+ * **Topic:** use SpeedMultiMeter and utils functions ([3e0293c](https://github.com/ydb-platform/ydb-embedded-ui/commit/3e0293cc5cf69c2dee5b6c4cdcf053829960dac5))
20
+ * **utils:** add formatBytesCustom function ([2f18c22](https://github.com/ydb-platform/ydb-embedded-ui/commit/2f18c2233b37b666e16327af0ca8e20bccf01de6))
21
+
3
22
  ## [3.3.4](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.3.3...v3.3.4) (2023-02-16)
4
23
 
5
24
 
@@ -3,7 +3,7 @@ import type {IResponseError} from '../../../types/api/error';
3
3
  import i18n from '../i18n';
4
4
 
5
5
  interface ResponseErrorProps {
6
- error: IResponseError;
6
+ error?: IResponseError;
7
7
  className?: string;
8
8
  defaultMessage?: string;
9
9
  }
@@ -13,5 +13,5 @@ export const ResponseError = ({
13
13
  className,
14
14
  defaultMessage = i18n('responseError.defaultMessage'),
15
15
  }: ResponseErrorProps) => {
16
- return <div className={`error ${className}`}>{error.statusText || defaultMessage}</div>;
16
+ return <div className={`error ${className}`}>{error?.statusText || defaultMessage}</div>;
17
17
  };
@@ -1,45 +1,24 @@
1
- import type {MultipleWindowsStat} from '../../../types/api/consumer';
2
1
  import type {TopicStats} from '../../../types/api/topic';
3
2
  import {formatBytes} from '../../../utils';
4
- import {DAY_IN_SECONDS, HOUR_IN_SECONDS, MINUTE_IN_SECONDS} from '../../../utils/constants';
5
-
6
3
  import {
7
- parseProtobufTimestampToMs,
8
- parseProtobufDurationToMs,
4
+ parseLag,
5
+ parseTimestampToIdleTime,
9
6
  formatDurationToShortTimeFormat,
10
7
  } from '../../../utils/timeParsers';
8
+ import {convertBytesObjectToSpeed} from '../../../utils/bytesParsers';
11
9
 
12
- import {VerticalBars} from '../../VerticalBars/VerticalBars';
10
+ import {SpeedMultiMeter} from '../../SpeedMultiMeter';
13
11
 
14
12
  import {createInfoFormatter} from '../utils';
15
13
 
16
- export const prepareBytesWritten = (data?: MultipleWindowsStat) => {
17
- return {
18
- per_minute:
19
- data && data.per_minute ? Math.floor(Number(data.per_minute) / MINUTE_IN_SECONDS) : 0,
20
- per_hour: data && data.per_hour ? Math.floor(Number(data.per_hour) / HOUR_IN_SECONDS) : 0,
21
- per_day: data && data.per_day ? Math.floor(Number(data.per_day) / DAY_IN_SECONDS) : 0,
22
- };
23
- };
24
-
25
14
  export const formatTopicStats = createInfoFormatter<TopicStats>({
26
15
  values: {
27
16
  store_size_bytes: formatBytes,
28
- min_last_write_time: (value) => {
29
- if (!value) {
30
- return formatDurationToShortTimeFormat(0);
31
- }
32
-
33
- const durationMs = Date.now() - parseProtobufTimestampToMs(value);
34
-
35
- // Duration could be negative because of the difference between server and local time
36
- // Usually it below 100ms, so it could be omitted
37
- return formatDurationToShortTimeFormat(durationMs < 0 ? 0 : durationMs);
38
- },
39
- max_write_time_lag: (value) =>
40
- formatDurationToShortTimeFormat(value ? parseProtobufDurationToMs(value) : 0),
17
+ min_last_write_time: (value) =>
18
+ formatDurationToShortTimeFormat(parseTimestampToIdleTime(value)),
19
+ max_write_time_lag: (value) => formatDurationToShortTimeFormat(parseLag(value)),
41
20
  bytes_written: (value) =>
42
- value && <VerticalBars values={Object.values(prepareBytesWritten(value))} />,
21
+ value && <SpeedMultiMeter data={convertBytesObjectToSpeed(value)} withValue={false} />,
43
22
  },
44
23
  labels: {
45
24
  store_size_bytes: 'Store size',
@@ -0,0 +1,20 @@
1
+ import type {ReactNode} from 'react';
2
+
3
+ import {HelpPopover} from '@gravity-ui/uikit';
4
+
5
+ interface LabelWithPopoverProps {
6
+ headerText: string;
7
+ popoverContent: ReactNode;
8
+ className?: string;
9
+ }
10
+
11
+ export const LabelWithPopover = ({
12
+ headerText,
13
+ popoverContent,
14
+ className,
15
+ }: LabelWithPopoverProps) => (
16
+ <div className={className}>
17
+ {headerText}
18
+ <HelpPopover content={popoverContent} />
19
+ </div>
20
+ );
@@ -0,0 +1 @@
1
+ export * from './LabelWithPopover';
@@ -0,0 +1,205 @@
1
+ const STICK_START = 30;
2
+ const STICK_SPACE = 70;
3
+ const HEIGHT = 54;
4
+ const WIDTH = 268;
5
+
6
+ const READ_FILL = '#ADE8F5';
7
+ const WRITE_FILL = '#f5be9d';
8
+
9
+ interface DashArcProps {
10
+ width: number;
11
+ height: number;
12
+ transform?: string;
13
+ }
14
+
15
+ const DashArc = ({width, height, transform}: DashArcProps) => (
16
+ <path
17
+ d={`M-${width / 2} 0 c0 -${height}, ${width} -${height}, ${width} 0`}
18
+ fill="none"
19
+ strokeDasharray="4,6"
20
+ stroke="#28f"
21
+ strokeWidth="1.6"
22
+ transform={transform}
23
+ />
24
+ );
25
+
26
+ interface ArrowLineProps {
27
+ width: number;
28
+ }
29
+
30
+ const ArrowLine = ({width}: ArrowLineProps) => (
31
+ <path fill="none" strokeWidth="2" d={`M0 0 h${width} l-10 -5 m0 10 l10 -5`} />
32
+ );
33
+
34
+ const WriteLag = () => (
35
+ <g fill="var(--yc-color-text-primary)" fontSize="12">
36
+ <g transform={`translate(0, ${HEIGHT / 2})`} stroke={WRITE_FILL}>
37
+ <ArrowLine width={STICK_SPACE * 2.9} />
38
+ </g>
39
+
40
+ <g transform={`translate(${STICK_START}, 0)`}>
41
+ <g transform={`translate(${STICK_SPACE / 2}, ${HEIGHT / 2})`}>
42
+ <DashArc width={STICK_SPACE} height={15} />
43
+ <text x="0" y="-15" textAnchor="middle">
44
+ <tspan x="0" dy="0">
45
+ write lag
46
+ </tspan>
47
+ </text>
48
+ </g>
49
+ <g transform={`translate(${STICK_SPACE * 1.7}, ${HEIGHT / 2})`}>
50
+ <DashArc width={STICK_SPACE * 1.4} height={15} />
51
+ <text x="0" y="-15" textAnchor="middle">
52
+ <tspan x="0" dy="0">
53
+ write idle time
54
+ </tspan>
55
+ </text>
56
+ </g>
57
+ </g>
58
+
59
+ <g transform={`translate(${STICK_START}, 0)`}>
60
+ <g transform={`translate(${0}, ${HEIGHT / 2})`}>
61
+ <use y="-10" xlinkHref="#check" stroke={WRITE_FILL} />
62
+ <text x="0" y="20" textAnchor="middle">
63
+ <tspan x="0" dy="0">
64
+ create time
65
+ </tspan>
66
+ </text>
67
+ </g>
68
+ <g transform={`translate(${STICK_SPACE}, ${HEIGHT / 2})`}>
69
+ <use y="-10" xlinkHref="#check" stroke={WRITE_FILL} />
70
+ <text x="0" y="20" textAnchor="middle">
71
+ <tspan x="0" dy="0">
72
+ write time
73
+ </tspan>
74
+ </text>
75
+ </g>
76
+ <g transform={`translate(${2.4 * STICK_SPACE}, ${HEIGHT / 2})`}>
77
+ <text x="0" y="20" textAnchor="middle">
78
+ <tspan x="0" dy="0">
79
+ now
80
+ </tspan>
81
+ </text>
82
+ </g>
83
+ </g>
84
+ </g>
85
+ );
86
+
87
+ const ReadLag = () => (
88
+ <g fill="var(--yc-color-text-primary)" fontSize="12">
89
+ <g transform={`translate(0, ${HEIGHT / 2})`} stroke={READ_FILL}>
90
+ <ArrowLine width={WIDTH} />
91
+ </g>
92
+
93
+ <g transform={`translate(${STICK_START}, 0)`}>
94
+ <g transform={`translate(${STICK_SPACE * 1.5}, ${HEIGHT / 2})`}>
95
+ <DashArc width={STICK_SPACE} height={15} />
96
+ <text x="0" y="-15" textAnchor="middle">
97
+ <tspan x="0" dy="0">
98
+ read lag
99
+ </tspan>
100
+ </text>
101
+ </g>
102
+ <g transform={`translate(${STICK_SPACE / 2}, ${HEIGHT / 2})`}>
103
+ <DashArc width={STICK_SPACE} height={15} />
104
+ <text x="0" y="-15" textAnchor="middle">
105
+ <tspan x="0" dy="0">
106
+ write lag
107
+ </tspan>
108
+ </text>
109
+ </g>
110
+ <g transform={`translate(${STICK_SPACE * 2.6}, ${HEIGHT / 2})`}>
111
+ <DashArc width={STICK_SPACE * 1.3} height={15} />
112
+ <text x="0" y="-15" textAnchor="middle">
113
+ <tspan x="0" dy="0">
114
+ read idle time
115
+ </tspan>
116
+ </text>
117
+ </g>
118
+ </g>
119
+
120
+ <g transform={`translate(${STICK_START}, ${HEIGHT / 2})`}>
121
+ <g transform={`translate(${0}, 0)`}>
122
+ <use y="-10" xlinkHref="#check" stroke={READ_FILL} />
123
+ <text x="0" y="20" textAnchor="middle">
124
+ <tspan x="0" dy="0">
125
+ create time
126
+ </tspan>
127
+ </text>
128
+ </g>
129
+ <g transform={`translate(${STICK_SPACE}, 0)`}>
130
+ <use y="-10" xlinkHref="#check" stroke={READ_FILL} />
131
+ <text x="0" y="20" textAnchor="middle">
132
+ <tspan x="0" dy="0">
133
+ write time
134
+ </tspan>
135
+ </text>
136
+ </g>
137
+ <g transform={`translate(${2 * STICK_SPACE}, 0)`}>
138
+ <use x="-2" y="-10" xlinkHref="#check" stroke={READ_FILL} />
139
+ <text x="0" y="20" textAnchor="middle">
140
+ <tspan x="0" dy="0">
141
+ read time
142
+ </tspan>
143
+ </text>
144
+ </g>
145
+ <g transform={`translate(${3.2 * STICK_SPACE}, 0)`}>
146
+ <text x="0" y="20" textAnchor="middle">
147
+ <tspan x="0" dy="0">
148
+ now
149
+ </tspan>
150
+ </text>
151
+ </g>
152
+ </g>
153
+ </g>
154
+ );
155
+
156
+ interface DashPatternProps {
157
+ id: string;
158
+ fill: string;
159
+ }
160
+
161
+ const DashPattern = ({id, fill}: DashPatternProps) => (
162
+ <pattern id={id} x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
163
+ <path d="M0 5L5 0H8L0 8V5M5 8L8 5V8Z" fill={fill} />
164
+ </pattern>
165
+ );
166
+
167
+ export const WriteLagImage = () => (
168
+ <svg
169
+ className="paint"
170
+ xmlns="http://www.w3.org/2000/svg"
171
+ xmlnsXlink="http://www.w3.org/1999/xlink"
172
+ viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
173
+ width={WIDTH}
174
+ height={HEIGHT}
175
+ >
176
+ <defs>
177
+ <g id="check">
178
+ <path d="M0 3 v14" strokeWidth="2" />
179
+ </g>
180
+ <DashPattern id="latest-read" fill={READ_FILL} />
181
+ <DashPattern id="latest-write" fill={WRITE_FILL} />
182
+ </defs>
183
+ <WriteLag />
184
+ </svg>
185
+ );
186
+
187
+ export const ReadLagImage = () => (
188
+ <svg
189
+ className="paint"
190
+ xmlns="http://www.w3.org/2000/svg"
191
+ xmlnsXlink="http://www.w3.org/1999/xlink"
192
+ viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
193
+ width={WIDTH}
194
+ height={HEIGHT}
195
+ >
196
+ <defs>
197
+ <g id="check">
198
+ <path d="M0 3 v14" strokeWidth="2" />
199
+ </g>
200
+ <DashPattern id="latest-read" fill={READ_FILL} />
201
+ <DashPattern id="latest-write" fill={WRITE_FILL} />
202
+ </defs>
203
+ <ReadLag />
204
+ </svg>
205
+ );
@@ -0,0 +1 @@
1
+ export * from './LagImages';
@@ -0,0 +1,92 @@
1
+ .speed-multimeter {
2
+ display: flex;
3
+
4
+ width: 100%;
5
+
6
+ &__content {
7
+ display: flex;
8
+ flex-grow: 1;
9
+ flex-direction: row;
10
+ justify-content: flex-end;
11
+
12
+ line-height: 22px;
13
+ }
14
+
15
+ &__displayed-value {
16
+ display: flex;
17
+ flex-direction: row;
18
+ justify-content: flex-end;
19
+
20
+ margin-right: 10px;
21
+ }
22
+
23
+ &__bars {
24
+ display: flex;
25
+ overflow: hidden;
26
+ flex-direction: column;
27
+ align-items: flex-start;
28
+
29
+ width: 32px;
30
+ margin-right: 5px;
31
+ }
32
+
33
+ &__bar-container {
34
+ width: 100%;
35
+ height: 6px;
36
+
37
+ &_highlighted {
38
+ background: var(--yc-color-line-generic);
39
+ }
40
+ }
41
+
42
+ &__bar {
43
+ min-width: 2px;
44
+ height: 100%;
45
+
46
+ &_color_light {
47
+ background: var(--yc-color-infographics-info-medium);
48
+ }
49
+
50
+ &_color_dark {
51
+ background: var(--yc-color-infographics-info-heavy);
52
+ }
53
+ }
54
+
55
+ &__bar-container + &__bar-container {
56
+ margin-top: 2px;
57
+ }
58
+
59
+ &__popover-container {
60
+ display: flex;
61
+ justify-content: center;
62
+ align-items: center;
63
+ }
64
+
65
+ &__popover-content {
66
+ padding: 10px;
67
+ }
68
+
69
+ &__popover-header {
70
+ display: block;
71
+
72
+ margin-bottom: 7px;
73
+
74
+ font-size: 18px;
75
+ line-height: 24px;
76
+ }
77
+
78
+ &__popover-row {
79
+ display: block;
80
+
81
+ font-size: 13px;
82
+ line-height: 18px;
83
+
84
+ &_color_primary {
85
+ color: var(--yc-color-text-primary);
86
+ }
87
+
88
+ &_color_secondary {
89
+ color: var(--yc-color-text-secondary);
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,120 @@
1
+ import {useState} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+ import {Popover} from '@gravity-ui/uikit';
4
+
5
+ import {formatBytesCustom, IBytesSizes, IProcessSpeedStats} from '../../utils/bytesParsers';
6
+
7
+ import './SpeedMultiMeter.scss';
8
+
9
+ import i18n from './i18n';
10
+
11
+ const b = cn('speed-multimeter');
12
+
13
+ interface SpeedMultiMeterProps {
14
+ data?: IProcessSpeedStats;
15
+ speedSize?: IBytesSizes;
16
+ withValue?: boolean;
17
+ withPopover?: boolean;
18
+ }
19
+
20
+ export const SpeedMultiMeter = ({
21
+ data,
22
+ speedSize = 'kb',
23
+ withValue = true,
24
+ withPopover = true,
25
+ }: SpeedMultiMeterProps) => {
26
+ const {perMinute = 0, perHour = 0, perDay = 0} = data || {};
27
+ const rawValues = [perMinute, perHour, perDay];
28
+
29
+ const formatValue = (value: number) =>
30
+ formatBytesCustom({value: value, size: speedSize, isSpeed: true});
31
+
32
+ const formattedValues = [
33
+ {value: formatValue(perMinute), label: i18n('perMinute')},
34
+ {value: formatValue(perHour), label: i18n('perHour')},
35
+ {value: formatValue(perDay), label: i18n('perDay')},
36
+ ];
37
+
38
+ const [valueToDisplay, setValueToDisplay] = useState(perMinute);
39
+ const [highlightedValueIndex, setHighlightedValueIndex] = useState(withValue ? 0 : undefined);
40
+ const [highlightedContainerIndex, setHighlightedContainerIndex] = useState<
41
+ number | undefined
42
+ >();
43
+
44
+ const onEnterDiagram = (values: number[], index: number) => {
45
+ setValueToDisplay(values[index]);
46
+ setHighlightedValueIndex(index);
47
+ setHighlightedContainerIndex(index);
48
+ };
49
+
50
+ const onLeaveDiagram = () => {
51
+ setValueToDisplay(perMinute);
52
+ setHighlightedValueIndex(withValue ? 0 : undefined);
53
+ setHighlightedContainerIndex(undefined);
54
+ };
55
+
56
+ const isValueHighlighted = (index: number) => highlightedValueIndex === index;
57
+ const isContainerHighlighted = (index: number) => highlightedContainerIndex === index;
58
+
59
+ const getModifier = (flag: boolean) => (flag ? {color: 'primary'} : {color: 'secondary'});
60
+
61
+ const renderValues = () => {
62
+ const max = Math.max(...rawValues, 0) || 1;
63
+
64
+ return rawValues.map((value, index) => (
65
+ <div
66
+ key={index}
67
+ className={b('bar-container', {
68
+ highlighted: isContainerHighlighted(index),
69
+ })}
70
+ onMouseEnter={onEnterDiagram.bind(null, rawValues, index)}
71
+ >
72
+ <div
73
+ className={b('bar', {
74
+ color: isValueHighlighted(index) ? 'dark' : 'light',
75
+ })}
76
+ style={{width: `${(100 * value) / max}%`}}
77
+ />
78
+ </div>
79
+ ));
80
+ };
81
+
82
+ const renderPopoverContent = () => {
83
+ return (
84
+ <div className={b('popover-content')}>
85
+ <span className={b('popover-header')}>{i18n('averageSpeed')}</span>
86
+ {formattedValues.map((formattedValue, index) => (
87
+ <span
88
+ key={index}
89
+ className={b('popover-row', getModifier(isValueHighlighted(index)))}
90
+ >
91
+ {`${formattedValue.label}: ${formattedValue.value}`}
92
+ </span>
93
+ ))}
94
+ </div>
95
+ );
96
+ };
97
+
98
+ return (
99
+ <div className={b()}>
100
+ <div className={b('content')}>
101
+ {withValue && (
102
+ <div className={b('displayed-value')}>{formatValue(valueToDisplay)}</div>
103
+ )}
104
+ <div className={b('popover-container')}>
105
+ <Popover
106
+ content={renderPopoverContent()}
107
+ placement={'bottom'}
108
+ disabled={!withPopover}
109
+ hasArrow={true}
110
+ size="s"
111
+ >
112
+ <div className={b('bars')} onMouseLeave={onLeaveDiagram}>
113
+ {renderValues()}
114
+ </div>
115
+ </Popover>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ );
120
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "averageSpeed": "Average speed",
3
+ "perMinute": "per minute",
4
+ "perHour": "per hour",
5
+ "perDay": "per day"
6
+ }
@@ -0,0 +1,13 @@
1
+ import {i18n, Lang} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-components-speed-multimeter';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
12
+
13
+
@@ -0,0 +1,6 @@
1
+ {
2
+ "averageSpeed": "Средняя скорость",
3
+ "perMinute": "за минуту",
4
+ "perHour": "за час",
5
+ "perDay": "за день"
6
+ }
@@ -0,0 +1 @@
1
+ export * from './SpeedMultiMeter';
@@ -16,7 +16,7 @@ import {VisibleEntities} from '../../../store/reducers/storage';
16
16
  import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
17
17
  //@ts-ignore
18
18
  import {stringifyVdiskId} from '../../../utils';
19
- import {getUsage, isFullDonorData} from '../../../utils/storage';
19
+ import {getUsage, isFullVDiksData} from '../../../utils/storage';
20
20
 
21
21
  import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
22
22
  import {VDisk} from '../VDisk';
@@ -257,26 +257,30 @@ function StorageGroups({
257
257
  render: ({value, row}) => (
258
258
  <div className={b('vdisks-wrapper')}>
259
259
  {_.map(value as TVDiskStateInfo[], (el) => {
260
- const donors = Array.isArray(el.Donors)
261
- ? el.Donors.filter(isFullDonorData)
262
- : [];
260
+ const donors = el.Donors;
263
261
 
264
- return donors.length > 0 ? (
262
+ return donors && donors.length > 0 ? (
265
263
  <Stack className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
266
264
  <VDisk
267
265
  data={el}
268
266
  poolName={row[TableColumnsIds.PoolName]}
269
267
  nodes={nodes}
270
268
  />
271
- {donors.map((donor) => (
272
- <VDisk
273
- data={donor}
274
- // donor and acceptor are always in the same group
275
- poolName={row[TableColumnsIds.PoolName]}
276
- nodes={nodes}
277
- key={stringifyVdiskId(donor.VDiskId)}
278
- />
279
- ))}
269
+ {donors.map((donor) => {
270
+ const isFullData = isFullVDiksData(donor);
271
+
272
+ return (
273
+ <VDisk
274
+ data={isFullData ? donor : {...donor, DonorMode: true}}
275
+ // donor and acceptor are always in the same group
276
+ poolName={row[TableColumnsIds.PoolName]}
277
+ nodes={nodes}
278
+ key={stringifyVdiskId(
279
+ isFullData ? donor.VDiskId : donor,
280
+ )}
281
+ />
282
+ );
283
+ })}
280
284
  </Stack>
281
285
  ) : (
282
286
  <div className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>