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
@@ -1,20 +1,37 @@
1
1
  import cn from 'bem-cn-lite';
2
- import DataTable from '@yandex-cloud/react-data-table';
2
+ import DataTable, {Column} from '@yandex-cloud/react-data-table';
3
3
  import {Button, Popover} from '@gravity-ui/uikit';
4
4
 
5
- import Icon from '../components/Icon/Icon';
6
- import EntityStatus from '../components/EntityStatus/EntityStatus';
7
- import PoolsGraph from '../components/PoolsGraph/PoolsGraph';
8
- import ProgressViewer from '../components/ProgressViewer/ProgressViewer';
9
- import {TabletsStatistic} from '../components/TabletsStatistic';
5
+ import Icon from '../../components/Icon/Icon';
6
+ import EntityStatus from '../../components/EntityStatus/EntityStatus';
7
+ import PoolsGraph from '../../components/PoolsGraph/PoolsGraph';
8
+ import ProgressViewer from '../../components/ProgressViewer/ProgressViewer';
9
+ import {TabletsStatistic} from '../../components/TabletsStatistic';
10
10
 
11
- import {getDefaultNodePath} from '../containers/Node/NodePages';
12
- import {formatBytes} from './index';
11
+ import {formatBytes} from '../../utils/index';
12
+ import {INodesPreparedEntity} from '../../types/store/nodes';
13
+ import {showTooltip as externalShowTooltip} from '../../store/reducers/tooltip';
13
14
 
14
- const b = cn('kv-nodes');
15
+ import {getDefaultNodePath} from '../Node/NodePages';
15
16
 
16
- export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeRef}) {
17
- const columns = [
17
+ import './NodesTable.scss';
18
+
19
+ const b = cn('ydb-nodes-table');
20
+
21
+ interface GetNodesColumnsProps {
22
+ showTooltip: (...args: Parameters<typeof externalShowTooltip>) => void;
23
+ hideTooltip: VoidFunction;
24
+ tabletsPath?: string;
25
+ getNodeRef?: Function;
26
+ }
27
+
28
+ export function getNodesColumns({
29
+ showTooltip,
30
+ hideTooltip,
31
+ tabletsPath,
32
+ getNodeRef,
33
+ }: GetNodesColumnsProps) {
34
+ const columns: Column<INodesPreparedEntity>[] = [
18
35
  {
19
36
  name: 'NodeId',
20
37
  header: '#',
@@ -23,19 +40,20 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
23
40
  },
24
41
  {
25
42
  name: 'Host',
26
- render: ({row, value}) => {
43
+ render: ({row}) => {
27
44
  const nodeRef = getNodeRef ? getNodeRef(row) + 'internal' : undefined;
28
-
29
- if (typeof value === 'undefined') {
45
+ if (typeof row.Host === 'undefined') {
30
46
  return <span>—</span>;
31
47
  }
32
48
  return (
33
49
  <div className={b('host-name-wrapper')}>
34
50
  <EntityStatus
35
51
  name={row.Host}
36
- onNameMouseEnter={(e) => showTooltip(e.target, row, 'nodeEndpoints')}
52
+ onNameMouseEnter={(e: MouseEvent) =>
53
+ showTooltip(e.target, row, 'nodeEndpoints')
54
+ }
37
55
  onNameMouseLeave={hideTooltip}
38
- status={row.Overall}
56
+ status={row.SystemState}
39
57
  path={getDefaultNodePath(row.NodeId)}
40
58
  hasClipboardButton
41
59
  className={b('host-name')}
@@ -60,28 +78,28 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
60
78
  name: 'DataCenter',
61
79
  header: 'DC',
62
80
  align: DataTable.LEFT,
63
- render: ({value}) => (value ? value : '—'),
81
+ render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
64
82
  width: '60px',
65
83
  },
66
84
  {
67
85
  name: 'Rack',
68
86
  header: 'Rack',
69
87
  align: DataTable.LEFT,
70
- render: ({value}) => (value ? value : '—'),
88
+ render: ({row}) => (row.Rack ? row.Rack : '—'),
71
89
  width: '80px',
72
90
  },
73
91
  {
74
92
  name: 'Version',
75
93
  width: '200px',
76
94
  align: DataTable.LEFT,
77
- render: ({value}) => {
78
- return <Popover content={value}>{value}</Popover>;
95
+ render: ({row}) => {
96
+ return <Popover content={row.Version}>{row.Version}</Popover>;
79
97
  },
80
98
  },
81
99
  {
82
- name: 'uptime',
100
+ name: 'Uptime',
83
101
  header: 'Uptime',
84
- sortAccessor: ({StartTime}) => -StartTime,
102
+ sortAccessor: ({StartTime}) => StartTime && -StartTime,
85
103
  align: DataTable.LEFT,
86
104
  width: '110px',
87
105
  },
@@ -90,12 +108,9 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
90
108
  header: 'Memory',
91
109
  sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
92
110
  defaultOrder: DataTable.DESCENDING,
93
- render: ({value, row}) => {
94
- if (value) {
95
- return formatBytes(value);
96
- }
97
- if (row.Metrics) {
98
- return formatBytes(row.Metrics.Memory);
111
+ render: ({row}) => {
112
+ if (row.MemoryUsed) {
113
+ return formatBytes(row.MemoryUsed);
99
114
  } else {
100
115
  return '—';
101
116
  }
@@ -107,14 +122,20 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
107
122
  name: 'PoolStats',
108
123
  header: 'CPU',
109
124
  sortAccessor: ({PoolStats = []}) =>
110
- PoolStats.reduce((acc, item) => acc + item.Usage, 0),
125
+ PoolStats.reduce((acc, item) => {
126
+ if (item.Usage) {
127
+ return acc + item.Usage;
128
+ } else {
129
+ return acc;
130
+ }
131
+ }, 0),
111
132
  defaultOrder: DataTable.DESCENDING,
112
- render: ({value}) =>
113
- value ? (
133
+ render: ({row}) =>
134
+ row.PoolStats ? (
114
135
  <PoolsGraph
115
136
  onMouseEnter={showTooltip}
116
137
  onMouseLeave={hideTooltip}
117
- pools={value}
138
+ pools={row.PoolStats}
118
139
  />
119
140
  ) : (
120
141
  '—'
@@ -128,9 +149,13 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
128
149
  sortAccessor: ({LoadAverage = []}) =>
129
150
  LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
130
151
  defaultOrder: DataTable.DESCENDING,
131
- render: ({value}) =>
132
- value && value.length > 0 ? (
133
- <ProgressViewer value={value[0]} percents={true} colorizeProgress={true} />
152
+ render: ({row}) =>
153
+ row.LoadAverage && row.LoadAverage.length > 0 ? (
154
+ <ProgressViewer
155
+ value={row.LoadAverage[0]}
156
+ percents={true}
157
+ colorizeProgress={true}
158
+ />
134
159
  ) : (
135
160
  '—'
136
161
  ),
@@ -0,0 +1,3 @@
1
+ {
2
+ "empty.default": "No such nodes"
3
+ }
@@ -0,0 +1,11 @@
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-nodes';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,3 @@
1
+ {
2
+ "empty.default": "Нет узлов"
3
+ }
@@ -0,0 +1 @@
1
+ export * from './Nodes';
@@ -1,7 +1,7 @@
1
1
  .storage-disk-progress-bar {
2
2
  $block: &;
3
3
 
4
- $border-width: 2px;
4
+ $border-width: 1px;
5
5
  $outer-border-radius: 4px;
6
6
  $inner-border-radius: $outer-border-radius - $border-width;
7
7
 
@@ -10,7 +10,7 @@
10
10
  display: block;
11
11
 
12
12
  min-width: 50px;
13
- height: var(--yc-text-body-2-line-height);
13
+ height: var(--yc-text-body-3-line-height);
14
14
 
15
15
  text-align: center;
16
16
 
@@ -19,6 +19,17 @@
19
19
  border-radius: $outer-border-radius;
20
20
  background-color: var(--yc-color-infographics-misc-light);
21
21
 
22
+ &_compact {
23
+ min-width: 0;
24
+ height: 12px;
25
+
26
+ border-radius: 2px;
27
+
28
+ #{$block}__filled {
29
+ border-radius: 1px;
30
+ }
31
+ }
32
+
22
33
  #{$block}__filled {
23
34
  background-color: var(--yc-color-infographics-misc-medium);
24
35
  }
@@ -89,25 +100,8 @@
89
100
 
90
101
  font-size: var(--yc-text-body-1-font-size);
91
102
  // bar height minus borders
92
- line-height: calc(var(--yc-text-body-2-line-height) - #{$border-width * 2});
103
+ line-height: calc(var(--yc-text-body-3-line-height) - #{$border-width * 2});
93
104
 
94
105
  color: inherit;
95
106
  }
96
-
97
- &__link {
98
- display: flex;
99
- justify-content: center;
100
-
101
- // extend active area to include borders
102
- height: var(--yc-text-body-2-line-height);
103
- margin: -$border-width;
104
- padding: $border-width;
105
-
106
- color: inherit;
107
- border-radius: $outer-border-radius;
108
-
109
- &:hover {
110
- color: inherit;
111
- }
112
- }
113
107
  }
@@ -9,33 +9,49 @@ import './DiskStateProgressBar.scss';
9
9
 
10
10
  const b = cn('storage-disk-progress-bar');
11
11
 
12
- export const diskProgressColors = {
13
- 0: 'Grey',
14
- 1: 'Green',
15
- 2: 'Blue',
16
- 3: 'Yellow',
17
- 4: 'Orange',
18
- 5: 'Red',
19
- };
12
+ // numeric enum to allow ordinal comparison
13
+ export enum EDiskStateSeverity {
14
+ Grey = 0,
15
+ Green = 1,
16
+ Blue = 2,
17
+ Yellow = 3,
18
+ Orange = 4,
19
+ Red = 5,
20
+ }
21
+
22
+ const severityToColor = Object.entries(EDiskStateSeverity).reduce(
23
+ (acc, [color, severity]) => ({...acc, [severity]: color}),
24
+ {} as Record<EDiskStateSeverity, keyof typeof EDiskStateSeverity>,
25
+ );
20
26
 
21
27
  interface DiskStateProgressBarProps {
22
28
  diskAllocatedPercent?: number;
23
- severity?: keyof typeof diskProgressColors;
29
+ severity?: EDiskStateSeverity;
30
+ compact?: boolean;
24
31
  }
25
32
 
26
- function DiskStateProgressBar({
33
+ export function DiskStateProgressBar({
27
34
  diskAllocatedPercent = -1,
28
35
  severity,
36
+ compact,
29
37
  }: DiskStateProgressBarProps) {
30
38
  const inverted = useSelector((state) => JSON.parse(getSettingValue(state, INVERTED_DISKS_KEY)));
31
39
 
32
40
  const renderAllocatedPercent = () => {
41
+ if (compact) {
42
+ return <div className={b('filled')} style={{width: '100%'}} />;
43
+ }
44
+
33
45
  return (
34
46
  diskAllocatedPercent >= 0 && (
35
47
  <React.Fragment>
36
48
  <div
37
49
  className={b('filled')}
38
- style={{width: `${inverted ? 100 - diskAllocatedPercent : diskAllocatedPercent}%`}}
50
+ style={{
51
+ width: `${
52
+ inverted ? 100 - diskAllocatedPercent : diskAllocatedPercent
53
+ }%`,
54
+ }}
39
55
  />
40
56
  <div className={b('filled-title')}>
41
57
  {`${Math.round(diskAllocatedPercent)}%`}
@@ -45,9 +61,11 @@ function DiskStateProgressBar({
45
61
  );
46
62
  };
47
63
 
48
- const mods: Record<string, boolean | undefined> = {inverted};
49
- if (severity !== undefined && severity in diskProgressColors) {
50
- mods[diskProgressColors[severity].toLocaleLowerCase()] = true;
64
+ const mods: Record<string, boolean | undefined> = {inverted, compact};
65
+
66
+ const color = severity !== undefined && severityToColor[severity];
67
+ if (color) {
68
+ mods[color.toLocaleLowerCase()] = true;
51
69
  }
52
70
 
53
71
  return (
@@ -63,5 +81,3 @@ function DiskStateProgressBar({
63
81
  </div>
64
82
  );
65
83
  }
66
-
67
- export default DiskStateProgressBar;
@@ -0,0 +1 @@
1
+ export * from './DiskStateProgressBar';
@@ -3,14 +3,24 @@
3
3
 
4
4
  width: 120px;
5
5
 
6
- border-radius: 4px; // to match interactive area with disk shape
7
-
8
6
  &__content {
7
+ position: relative;
8
+
9
9
  border-radius: 4px; // to match interactive area with disk shape
10
10
  }
11
11
 
12
- &__popup-wrapper {
13
- padding: 12px;
12
+ &__vdisks {
13
+ display: flex;
14
+ // this breaks disks relative sizes, but disks rarely exceed one line
15
+ flex-wrap: wrap;
16
+ gap: 2px;
17
+
18
+ margin-bottom: 4px;
19
+ }
20
+
21
+ &__vdisks-item {
22
+ flex-basis: 5px;
23
+ flex-shrink: 0;
14
24
  }
15
25
 
16
26
  &__media-type {
@@ -19,6 +29,7 @@
19
29
  right: 4px;
20
30
 
21
31
  font-size: var(--yc-text-body-1-font-size);
32
+ line-height: var(--yc-text-body-3-line-height);
22
33
 
23
34
  color: var(--yc-color-text-secondary);
24
35
  }
@@ -0,0 +1,145 @@
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 {getVDisksForPDisk} from '../../../store/reducers/storage';
8
+ import {TPDiskStateInfo, TPDiskState} from '../../../types/api/pdisk';
9
+ import {TVDiskStateInfo} from '../../../types/api/vdisk';
10
+ import {stringifyVdiskId} from '../../../utils';
11
+ import {useTypedSelector} from '../../../utils/hooks';
12
+ import {getPDiskType} from '../../../utils/pdisk';
13
+
14
+ import {STRUCTURE} from '../../Node/NodePages';
15
+
16
+ import {DiskStateProgressBar, EDiskStateSeverity} from '../DiskStateProgressBar';
17
+ import {PDiskPopup} from '../PDiskPopup';
18
+ import {VDisk} from '../VDisk';
19
+
20
+ import {NOT_AVAILABLE_SEVERITY} from '../utils';
21
+
22
+ import './PDisk.scss';
23
+
24
+ const b = cn('pdisk-storage');
25
+
26
+ const stateSeverity = {
27
+ [TPDiskState.Initial]: EDiskStateSeverity.Grey,
28
+ [TPDiskState.Normal]: EDiskStateSeverity.Green,
29
+ [TPDiskState.InitialFormatRead]: EDiskStateSeverity.Yellow,
30
+ [TPDiskState.InitialSysLogRead]: EDiskStateSeverity.Yellow,
31
+ [TPDiskState.InitialCommonLogRead]: EDiskStateSeverity.Yellow,
32
+ [TPDiskState.InitialFormatReadError]: EDiskStateSeverity.Red,
33
+ [TPDiskState.InitialSysLogReadError]: EDiskStateSeverity.Red,
34
+ [TPDiskState.InitialSysLogParseError]: EDiskStateSeverity.Red,
35
+ [TPDiskState.InitialCommonLogReadError]: EDiskStateSeverity.Red,
36
+ [TPDiskState.InitialCommonLogParseError]: EDiskStateSeverity.Red,
37
+ [TPDiskState.CommonLoggerInitError]: EDiskStateSeverity.Red,
38
+ [TPDiskState.OpenFileError]: EDiskStateSeverity.Red,
39
+ [TPDiskState.ChunkQuotaError]: EDiskStateSeverity.Red,
40
+ [TPDiskState.DeviceIoError]: EDiskStateSeverity.Red,
41
+ };
42
+
43
+ interface PDiskProps {
44
+ nodeId: number;
45
+ data?: TPDiskStateInfo;
46
+ }
47
+
48
+ const isSeverityKey = (key?: TPDiskState): key is keyof typeof stateSeverity =>
49
+ key !== undefined && key in stateSeverity;
50
+
51
+ const getStateSeverity = (pDiskState?: TPDiskState) => {
52
+ return isSeverityKey(pDiskState) ? stateSeverity[pDiskState] : NOT_AVAILABLE_SEVERITY;
53
+ };
54
+
55
+ export const PDisk = ({nodeId, data: rawData = {}}: PDiskProps) => {
56
+ // NodeId in data is required for the popup
57
+ const data = useMemo(() => ({...rawData, NodeId: nodeId}), [rawData, nodeId]);
58
+
59
+ const vdisks: TVDiskStateInfo[] | undefined = useTypedSelector((state) =>
60
+ // @ts-expect-error selector is correct, but js infers broken type
61
+ // unignore after rewriting reducer in ts
62
+ getVDisksForPDisk(state, nodeId, data.PDiskId),
63
+ );
64
+
65
+ const [severity, setSeverity] = useState(getStateSeverity(data.State));
66
+ const [isPopupVisible, setIsPopupVisible] = useState(false);
67
+
68
+ const anchor = useRef(null);
69
+
70
+ useEffect(() => {
71
+ const newSeverity = getStateSeverity(data.State);
72
+ if (severity !== newSeverity) {
73
+ setSeverity(newSeverity);
74
+ }
75
+ }, [data.State]);
76
+
77
+ const showPopup = () => {
78
+ setIsPopupVisible(true);
79
+ };
80
+
81
+ const hidePopup = () => {
82
+ setIsPopupVisible(false);
83
+ };
84
+
85
+ const pdiskAllocatedPercent = useMemo(() => {
86
+ const {AvailableSize, TotalSize} = data;
87
+
88
+ if (!AvailableSize || !TotalSize) {
89
+ return undefined;
90
+ }
91
+
92
+ return !isNaN(Number(AvailableSize)) && !isNaN(Number(TotalSize))
93
+ ? Math.round(((Number(TotalSize) - Number(AvailableSize)) * 100) / Number(TotalSize))
94
+ : undefined;
95
+ }, [data]);
96
+
97
+ const renderVDisks = () => {
98
+ if (!vdisks?.length) {
99
+ return null;
100
+ }
101
+
102
+ return (
103
+ <div className={b('vdisks')}>
104
+ {vdisks.map((vdisk) => (
105
+ <div
106
+ key={stringifyVdiskId(vdisk.VDiskId)}
107
+ className={b('vdisks-item')}
108
+ style={{
109
+ // 1 is small enough for empty disks to be of the minimum width
110
+ // but if all of them are empty, `flex-grow: 1` would size them evenly
111
+ flexGrow: (Number(vdisk.AllocatedSize) || 1),
112
+ }}
113
+ >
114
+ <VDisk data={vdisk} compact />
115
+ </div>
116
+ ))}
117
+ </div>
118
+ );
119
+ };
120
+
121
+ return (
122
+ <React.Fragment>
123
+ <PDiskPopup data={data} anchorRef={anchor} open={isPopupVisible} />
124
+ <div className={b()} ref={anchor}>
125
+ {renderVDisks()}
126
+ <InternalLink
127
+ to={createHref(
128
+ routes.node,
129
+ {id: nodeId, activeTab: STRUCTURE},
130
+ {pdiskId: data.PDiskId || ''},
131
+ )}
132
+ className={b('content')}
133
+ onMouseEnter={showPopup}
134
+ onMouseLeave={hidePopup}
135
+ >
136
+ <DiskStateProgressBar
137
+ diskAllocatedPercent={pdiskAllocatedPercent}
138
+ severity={severity}
139
+ />
140
+ <div className={b('media-type')}>{getPDiskType(data)}</div>
141
+ </InternalLink>
142
+ </div>
143
+ </React.Fragment>
144
+ );
145
+ };
@@ -0,0 +1,37 @@
1
+ import {MemoryRouter} from 'react-router-dom';
2
+
3
+ import {renderWithStore} from '../../../../utils/tests/providers';
4
+
5
+ import {TPDiskState} from '../../../../types/api/pdisk';
6
+
7
+ import {PDisk} from '../PDisk';
8
+
9
+ describe('PDisk state', () => {
10
+ it('Should determine severity based on State', () => {
11
+ const {getAllByRole} = renderWithStore(
12
+ <MemoryRouter>
13
+ <PDisk nodeId={1} data={{State: TPDiskState.Normal}} />
14
+ <PDisk nodeId={2} data={{State: TPDiskState.ChunkQuotaError}} />
15
+ </MemoryRouter>,
16
+ );
17
+
18
+ const [normalDisk, erroredDisk] = getAllByRole('meter');
19
+
20
+ expect(normalDisk.className).not.toBe(erroredDisk.className);
21
+ });
22
+
23
+ it('Should display as unavailabe when no State is provided', () => {
24
+ const {getAllByRole} = renderWithStore(
25
+ <MemoryRouter>
26
+ <PDisk nodeId={1} />
27
+ <PDisk nodeId={2} data={{State: TPDiskState.ChunkQuotaError}} />
28
+ </MemoryRouter>,
29
+ );
30
+
31
+ const [disk1, disk2] = getAllByRole('meter');
32
+
33
+ // unavailable disks display with the grey color
34
+ expect(disk1.className).toMatch(/_grey\b/i);
35
+ expect(disk2.className).not.toMatch(/_grey\b/i);
36
+ });
37
+ });
@@ -0,0 +1 @@
1
+ export * from './PDisk';
@@ -0,0 +1,3 @@
1
+ .pdisk-storage-popup {
2
+ padding: 12px;
3
+ }
@@ -0,0 +1,82 @@
1
+ import {useMemo} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+
4
+ import {Popup, PopupProps} from '@gravity-ui/uikit';
5
+
6
+ import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer';
7
+
8
+ import {EFlag} from '../../../types/api/enums';
9
+ import {TPDiskStateInfo} from '../../../types/api/pdisk';
10
+ import {getPDiskId} from '../../../utils';
11
+ import {getPDiskType} from '../../../utils/pdisk';
12
+ import {bytesToGB} from '../../../utils/utils';
13
+
14
+ import './PDiskPopup.scss';
15
+
16
+ const b = cn('pdisk-storage-popup');
17
+
18
+ const errorColors = [EFlag.Orange, EFlag.Red, EFlag.Yellow];
19
+
20
+ export type NodesHosts = {
21
+ // NodeId => Host
22
+ [nodeId: number]: string;
23
+ }
24
+
25
+ export const preparePDiskData = (data: TPDiskStateInfo, nodes?: NodesHosts) => {
26
+ const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Device} = data;
27
+
28
+ const pdiskData: InfoViewerItem[] = [
29
+ {label: 'PDisk', value: getPDiskId({NodeId, PDiskId})},
30
+ {label: 'State', value: State || 'not available'},
31
+ {label: 'Type', value: getPDiskType(data) || 'unknown'},
32
+ ];
33
+
34
+ if (NodeId) {
35
+ pdiskData.push({label: 'Node Id', value: NodeId});
36
+ }
37
+
38
+ if (nodes && NodeId && nodes[NodeId]) {
39
+ pdiskData.push({label: 'Host', value: nodes[NodeId]});
40
+ }
41
+
42
+ if (Path) {
43
+ pdiskData.push({label: 'Path', value: Path});
44
+ }
45
+
46
+ pdiskData.push({
47
+ label: 'Available',
48
+ value: `${bytesToGB(AvailableSize)} of ${bytesToGB(TotalSize)}`,
49
+ });
50
+
51
+ if (Realtime && errorColors.includes(Realtime)) {
52
+ pdiskData.push({label: 'Realtime', value: Realtime});
53
+ }
54
+
55
+ if (Device && errorColors.includes(Device)) {
56
+ pdiskData.push({label: 'Device', value: Device});
57
+ }
58
+
59
+ return pdiskData;
60
+ };
61
+
62
+ interface PDiskPopupProps extends PopupProps {
63
+ data: TPDiskStateInfo;
64
+ nodes?: NodesHosts;
65
+ }
66
+
67
+ export const PDiskPopup = ({data, nodes, ...props}: PDiskPopupProps) => {
68
+ const info = useMemo(() => preparePDiskData(data, nodes), [data, nodes]);
69
+
70
+ return (
71
+ <Popup
72
+ className={b()}
73
+ placement={['top', 'bottom']}
74
+ // bigger offset for easier switching to neighbour nodes
75
+ // matches the default offset for popup with arrow out of a sense of beauty
76
+ offset={[0, 12]}
77
+ {...props}
78
+ >
79
+ <InfoViewer title="PDisk" info={info} size="s" />
80
+ </Popup>
81
+ );
82
+ };
@@ -0,0 +1 @@
1
+ export * from './PDiskPopup';
@@ -32,7 +32,7 @@ import {
32
32
  getStorageNodesCount,
33
33
  getUsageFilterOptions,
34
34
  } from '../../store/reducers/storage';
35
- import {getNodesList} from '../../store/reducers/clusterNodes';
35
+ import {getNodesList} from '../../store/reducers/nodesList';
36
36
  import StorageGroups from './StorageGroups/StorageGroups';
37
37
  import StorageNodes from './StorageNodes/StorageNodes';
38
38
  import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';