ydb-embedded-ui 3.3.4 → 3.4.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 (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)}>