ydb-embedded-ui 4.9.0 → 4.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +7 -4
  3. package/dist/components/EntityStatus/EntityStatus.js +3 -1
  4. package/dist/components/FormattedBytes/FormattedBytes.tsx +10 -0
  5. package/dist/components/FormattedBytes/utils.tsx +13 -0
  6. package/dist/components/FullNodeViewer/FullNodeViewer.tsx +73 -0
  7. package/dist/components/InfoViewer/formatters/schema.ts +3 -1
  8. package/dist/components/InfoViewer/formatters/table.ts +6 -5
  9. package/dist/components/ProblemFilter/ProblemFilter.tsx +2 -2
  10. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +4 -4
  11. package/dist/components/TableWithControlsLayout/TableWithControlsLayout.scss +32 -0
  12. package/dist/components/TableWithControlsLayout/TableWithControlsLayout.tsx +43 -0
  13. package/dist/components/TruncatedQuery/{TruncatedQuery.js → TruncatedQuery.tsx} +10 -8
  14. package/dist/containers/AsideNavigation/AsideNavigation.tsx +8 -8
  15. package/dist/containers/Cluster/Cluster.scss +4 -5
  16. package/dist/containers/Cluster/Cluster.tsx +13 -28
  17. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +4 -0
  18. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +7 -0
  19. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +1 -1
  20. package/dist/containers/Cluster/utils.tsx +0 -11
  21. package/dist/containers/Header/Header.scss +1 -5
  22. package/dist/containers/Node/Node.tsx +3 -3
  23. package/dist/containers/Nodes/Nodes.scss +1 -24
  24. package/dist/containers/Nodes/Nodes.tsx +30 -40
  25. package/dist/containers/Storage/PDisk/PDisk.tsx +2 -7
  26. package/dist/containers/Storage/Storage.scss +1 -14
  27. package/dist/containers/Storage/Storage.tsx +237 -0
  28. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +45 -40
  29. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +12 -16
  30. package/dist/containers/Storage/StorageTypeFilter/StorageTypeFilter.tsx +3 -1
  31. package/dist/containers/Storage/{StorageVisibleEntityFilter/StorageVisibleEntityFilter.tsx → StorageVisibleEntitiesFilter/StorageVisibleEntitiesFilter.tsx} +4 -2
  32. package/dist/containers/Storage/UsageFilter/UsageFilter.scss +1 -0
  33. package/dist/containers/Storage/UsageFilter/UsageFilter.tsx +17 -17
  34. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +6 -2
  35. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -3
  36. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +7 -4
  37. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +0 -15
  38. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +10 -3
  39. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +3 -12
  40. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +2 -7
  41. package/dist/containers/Tenant/{Preview → Query/Preview}/Preview.scss +1 -1
  42. package/dist/containers/Tenant/Query/Preview/Preview.tsx +121 -0
  43. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +1 -1
  44. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +6 -8
  45. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +1 -1
  46. package/dist/containers/Tenant/Query/i18n/en.json +9 -2
  47. package/dist/containers/Tenant/Query/i18n/ru.json +9 -2
  48. package/dist/containers/Tenants/Tenants.scss +1 -13
  49. package/dist/containers/Tenants/Tenants.tsx +262 -0
  50. package/dist/services/api.ts +8 -3
  51. package/dist/store/reducers/nodes/nodes.ts +4 -112
  52. package/dist/store/reducers/nodes/selectors.ts +74 -0
  53. package/dist/store/reducers/nodes/utils.ts +46 -0
  54. package/dist/store/reducers/partitions/types.ts +3 -3
  55. package/dist/store/reducers/settings/settings.ts +4 -2
  56. package/dist/store/reducers/settings/types.ts +3 -1
  57. package/dist/store/reducers/storage/selectors.ts +279 -0
  58. package/dist/store/reducers/storage/storage.ts +191 -0
  59. package/dist/store/reducers/storage/types.ts +80 -0
  60. package/dist/store/reducers/tenants/selectors.ts +46 -0
  61. package/dist/store/reducers/tenants/tenants.ts +21 -14
  62. package/dist/store/reducers/tenants/types.ts +20 -5
  63. package/dist/store/reducers/tenants/utils.ts +68 -0
  64. package/dist/types/additionalProps.ts +8 -0
  65. package/dist/types/api/compute.ts +27 -2
  66. package/dist/types/api/nodes.ts +12 -1
  67. package/dist/types/api/schema/cdcStream.ts +32 -0
  68. package/dist/types/api/schema/columnEntity.ts +138 -0
  69. package/dist/types/api/schema/externalDataSource.ts +24 -0
  70. package/dist/types/api/schema/externalTable.ts +14 -0
  71. package/dist/types/api/schema/index.ts +10 -0
  72. package/dist/types/api/schema/persQueueGroup.ts +191 -0
  73. package/dist/types/api/schema/schema.ts +299 -0
  74. package/dist/types/api/schema/shared.ts +42 -0
  75. package/dist/types/api/schema/table.ts +616 -0
  76. package/dist/types/api/schema/tableIndex.ts +33 -0
  77. package/dist/types/api/storage.ts +1 -1
  78. package/dist/types/store/topic.ts +3 -3
  79. package/dist/utils/bytesParsers/__test__/formatBytes.test.ts +38 -0
  80. package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +2 -2
  81. package/dist/utils/bytesParsers/formatBytes.ts +132 -0
  82. package/dist/utils/bytesParsers/i18n/en.json +1 -0
  83. package/dist/utils/bytesParsers/i18n/ru.json +1 -0
  84. package/dist/utils/bytesParsers/index.ts +1 -1
  85. package/dist/utils/index.js +5 -10
  86. package/dist/utils/numeral.ts +8 -0
  87. package/package.json +1 -1
  88. package/dist/assets/icons/versions.svg +0 -3
  89. package/dist/components/FullNodeViewer/FullNodeViewer.js +0 -89
  90. package/dist/containers/Node/NodeOverview/NodeOverview.scss +0 -0
  91. package/dist/containers/Node/NodeOverview/NodeOverview.tsx +0 -21
  92. package/dist/containers/Storage/Storage.js +0 -350
  93. package/dist/containers/Tenant/Preview/Preview.js +0 -168
  94. package/dist/containers/Tenants/Tenants.js +0 -363
  95. package/dist/store/reducers/storage/storage.js +0 -404
  96. package/dist/types/api/schema.ts +0 -1326
  97. package/dist/utils/bytesParsers/formatBytesCustom.ts +0 -57
@@ -1,21 +1,10 @@
1
1
  @import '../../styles/mixins.scss';
2
2
 
3
3
  .ydb-nodes {
4
- overflow: auto;
5
- flex-grow: 1;
6
-
7
- height: 100%;
8
-
9
- @include flex-container();
10
-
11
4
  &__search {
12
5
  @include search();
13
6
  }
14
7
 
15
- &__controls {
16
- @include controls();
17
- }
18
-
19
8
  &__show-all-wrapper {
20
9
  position: sticky;
21
10
  left: 0;
@@ -23,19 +12,7 @@
23
12
  margin-bottom: 15px;
24
13
  }
25
14
 
26
- &__table-wrapper {
27
- overflow: auto;
28
- @include flex-container();
29
- }
30
-
31
- &__table-content {
32
- overflow: auto;
33
-
34
- height: 100%;
35
-
36
- @include freeze-nth-column(1);
37
- @include freeze-nth-column(2, 80px);
38
-
15
+ &__table {
39
16
  @include table-styles;
40
17
  @include table-sticky-styles;
41
18
  }
@@ -5,15 +5,16 @@ import {useDispatch} from 'react-redux';
5
5
  import DataTable from '@gravity-ui/react-data-table';
6
6
 
7
7
  import type {EPathType} from '../../types/api/schema';
8
- import type {ValueOf} from '../../types/common';
8
+ import type {ProblemFilterValue} from '../../store/reducers/settings/types';
9
9
 
10
10
  import {AccessDenied} from '../../components/Errors/403';
11
11
  import {Illustration} from '../../components/Illustration';
12
- import {Loader} from '../../components/Loader';
13
12
  import {Search} from '../../components/Search';
14
13
  import {ProblemFilter} from '../../components/ProblemFilter';
15
14
  import {UptimeFilter} from '../../components/UptimeFIlter';
16
15
  import {EntitiesCount} from '../../components/EntitiesCount';
16
+ import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
17
+ import {ResponseError} from '../../components/Errors/ResponseError';
17
18
 
18
19
  import {DEFAULT_TABLE_SETTINGS, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY} from '../../utils/constants';
19
20
  import {useAutofetcher, useSetting, useTypedSelector} from '../../utils/hooks';
@@ -21,12 +22,12 @@ import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '.
21
22
 
22
23
  import {
23
24
  getNodes,
24
- getFilteredPreparedNodesList,
25
25
  setNodesUptimeFilter,
26
26
  setSearchValue,
27
27
  resetNodesState,
28
28
  getComputeNodes,
29
29
  } from '../../store/reducers/nodes/nodes';
30
+ import {selectFilteredNodes} from '../../store/reducers/nodes/selectors';
30
31
  import {changeFilter, ProblemFilterValues} from '../../store/reducers/settings/settings';
31
32
 
32
33
  import {isDatabaseEntityType} from '../Tenant/utils/schema';
@@ -42,11 +43,10 @@ const b = cn('ydb-nodes');
42
43
  interface NodesProps {
43
44
  path?: string;
44
45
  type?: EPathType;
45
- className?: string;
46
46
  additionalNodesInfo?: AdditionalNodesInfo;
47
47
  }
48
48
 
49
- export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesProps) => {
49
+ export const Nodes = ({path, type, additionalNodesInfo = {}}: NodesProps) => {
50
50
  const dispatch = useDispatch();
51
51
 
52
52
  const isClusterNodes = !path;
@@ -63,7 +63,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
63
63
  const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
64
64
  const {autorefresh} = useTypedSelector((state) => state.schema);
65
65
 
66
- const nodes = useTypedSelector(getFilteredPreparedNodesList);
66
+ const nodes = useTypedSelector(selectFilteredNodes);
67
67
 
68
68
  const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
69
69
 
@@ -84,7 +84,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
84
84
  };
85
85
 
86
86
  const handleProblemFilterChange = (value: string) => {
87
- dispatch(changeFilter(value as ValueOf<typeof ProblemFilterValues>));
87
+ dispatch(changeFilter(value as ProblemFilterValue));
88
88
  };
89
89
 
90
90
  const handleUptimeFilterChange = (value: string) => {
@@ -93,7 +93,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
93
93
 
94
94
  const renderControls = () => {
95
95
  return (
96
- <div className={b('controls')}>
96
+ <>
97
97
  <Search
98
98
  onChange={handleSearchQueryChange}
99
99
  placeholder="Host name"
@@ -108,7 +108,7 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
108
108
  label={'Nodes'}
109
109
  loading={loading && !wasLoaded}
110
110
  />
111
- </div>
111
+ </>
112
112
  );
113
113
  };
114
114
 
@@ -127,44 +127,34 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
127
127
  }
128
128
 
129
129
  return (
130
- <div className={b('table-wrapper')}>
131
- <div className={b('table-content')}>
132
- <DataTable
133
- theme="yandex-cloud"
134
- data={nodes || []}
135
- columns={columns}
136
- settings={DEFAULT_TABLE_SETTINGS}
137
- initialSortOrder={{
138
- columnId: 'NodeId',
139
- order: DataTable.ASCENDING,
140
- }}
141
- emptyDataMessage={i18n('empty.default')}
142
- rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
143
- />
144
- </div>
145
- </div>
130
+ <DataTable
131
+ theme="yandex-cloud"
132
+ data={nodes || []}
133
+ columns={columns}
134
+ settings={DEFAULT_TABLE_SETTINGS}
135
+ initialSortOrder={{
136
+ columnId: 'NodeId',
137
+ order: DataTable.ASCENDING,
138
+ }}
139
+ emptyDataMessage={i18n('empty.default')}
140
+ rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
141
+ />
146
142
  );
147
143
  };
148
144
 
149
- const renderContent = () => {
150
- return (
151
- <div className={b(null, className)}>
152
- {renderControls()}
153
- {renderTable()}
154
- </div>
155
- );
156
- };
157
-
158
- if (loading && !wasLoaded) {
159
- return <Loader size={isClusterNodes ? 'l' : 'm'} />;
160
- }
161
-
162
145
  if (error) {
163
146
  if (error.status === 403) {
164
147
  return <AccessDenied />;
165
148
  }
166
- return <div>{error.statusText}</div>;
149
+ return <ResponseError error={error} />;
167
150
  }
168
151
 
169
- return renderContent();
152
+ return (
153
+ <TableWithControlsLayout>
154
+ <TableWithControlsLayout.Controls>{renderControls()}</TableWithControlsLayout.Controls>
155
+ <TableWithControlsLayout.Table loading={loading && !wasLoaded} className={b('table')}>
156
+ {renderTable()}
157
+ </TableWithControlsLayout.Table>
158
+ </TableWithControlsLayout>
159
+ );
170
160
  };
@@ -5,9 +5,8 @@ import {InternalLink} from '../../../components/InternalLink';
5
5
  import {Stack} from '../../../components/Stack/Stack';
6
6
 
7
7
  import routes, {createHref} from '../../../routes';
8
- import {getVDisksForPDisk} from '../../../store/reducers/storage/storage';
8
+ import {selectVDisksForPDisk} from '../../../store/reducers/storage/selectors';
9
9
  import {TPDiskStateInfo, TPDiskState} from '../../../types/api/pdisk';
10
- import {TVDiskStateInfo} from '../../../types/api/vdisk';
11
10
  import {stringifyVdiskId} from '../../../utils';
12
11
  import {useTypedSelector} from '../../../utils/hooks';
13
12
  import {getPDiskType} from '../../../utils/pdisk';
@@ -58,11 +57,7 @@ export const PDisk = ({nodeId, data: rawData = {}}: PDiskProps) => {
58
57
  // NodeId in data is required for the popup
59
58
  const data = useMemo(() => ({...rawData, NodeId: nodeId}), [rawData, nodeId]);
60
59
 
61
- const vdisks: TVDiskStateInfo[] | undefined = useTypedSelector((state) =>
62
- // @ts-expect-error selector is correct, but js infers broken type
63
- // unignore after rewriting reducer in ts
64
- getVDisksForPDisk(state, nodeId, data.PDiskId),
65
- );
60
+ const vdisks = useTypedSelector((state) => selectVDisksForPDisk(state, nodeId, data.PDiskId));
66
61
 
67
62
  const [severity, setSeverity] = useState(getStateSeverity(data.State));
68
63
  const [isPopupVisible, setIsPopupVisible] = useState(false);
@@ -1,24 +1,11 @@
1
1
  @import '../../styles/mixins.scss';
2
2
 
3
3
  .global-storage {
4
- height: 100%;
5
- max-height: 100%;
6
- @include flex-container;
7
-
8
- &__controls {
9
- @include controls();
10
- }
11
4
  &__search {
12
5
  @include search();
13
6
  }
14
7
 
15
- &__table-wrapper {
16
- overflow: auto;
17
- flex-grow: 1;
18
-
19
- font-size: var(--yc-text-body-2-font-size);
20
- line-height: var(--yc-text-body-2-line-height);
21
-
8
+ &__table {
22
9
  .yc-tooltip {
23
10
  // stylelint-disable-next-line declaration-no-important
24
11
  height: var(--yc-text-body-2-line-height) !important;
@@ -0,0 +1,237 @@
1
+ import {useCallback, useEffect} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
+ import cn from 'bem-cn-lite';
4
+
5
+ import DataTable, {Settings} from '@gravity-ui/react-data-table';
6
+
7
+ import {Search} from '../../components/Search';
8
+ import {UptimeFilter} from '../../components/UptimeFIlter';
9
+ import {AccessDenied} from '../../components/Errors/403';
10
+ import {EntitiesCount} from '../../components/EntitiesCount';
11
+ import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
12
+ import {ResponseError} from '../../components/Errors/ResponseError';
13
+
14
+ import type {StorageType, VisibleEntities} from '../../store/reducers/storage/types';
15
+ import {
16
+ getStorageInfo,
17
+ setInitialState,
18
+ setVisibleEntities,
19
+ setStorageTextFilter,
20
+ setUsageFilter,
21
+ setStorageType,
22
+ setNodesUptimeFilter,
23
+ setDataWasNotLoaded,
24
+ } from '../../store/reducers/storage/storage';
25
+ import {
26
+ selectFilteredGroups,
27
+ selectFilteredNodes,
28
+ selectStorageNodesCount,
29
+ selectStorageGroupsCount,
30
+ selectUsageFilterOptions,
31
+ } from '../../store/reducers/storage/selectors';
32
+ import {VISIBLE_ENTITIES, STORAGE_TYPES} from '../../store/reducers/storage/constants';
33
+ import {getNodesList, selectNodesMap} from '../../store/reducers/nodesList';
34
+ import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
35
+ import {AdditionalNodesInfo, NodesUptimeFilterValues} from '../../utils/nodes';
36
+ import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
37
+
38
+ import {StorageGroups} from './StorageGroups/StorageGroups';
39
+ import {StorageNodes} from './StorageNodes/StorageNodes';
40
+ import {StorageTypeFilter} from './StorageTypeFilter/StorageTypeFilter';
41
+ import {StorageVisibleEntitiesFilter} from './StorageVisibleEntitiesFilter/StorageVisibleEntitiesFilter';
42
+ import {UsageFilter} from './UsageFilter';
43
+
44
+ import './Storage.scss';
45
+
46
+ const b = cn('global-storage');
47
+
48
+ const tableSettings: Settings = {
49
+ ...DEFAULT_TABLE_SETTINGS,
50
+ defaultOrder: DataTable.DESCENDING,
51
+ };
52
+
53
+ interface StorageProps {
54
+ additionalNodesInfo?: AdditionalNodesInfo;
55
+ tenant?: string;
56
+ nodeId?: string;
57
+ }
58
+
59
+ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) => {
60
+ const dispatch = useDispatch();
61
+
62
+ const {autorefresh} = useTypedSelector((state) => state.schema);
63
+ const {
64
+ loading,
65
+ wasLoaded,
66
+ error,
67
+ type: storageType,
68
+ visible: visibleEntities,
69
+ filter,
70
+ usageFilter,
71
+ nodesUptimeFilter,
72
+ } = useTypedSelector((state) => state.storage);
73
+ const storageNodes = useTypedSelector(selectFilteredNodes);
74
+ const storageGroups = useTypedSelector(selectFilteredGroups);
75
+ const nodesCount = useTypedSelector(selectStorageNodesCount);
76
+ const groupsCount = useTypedSelector(selectStorageGroupsCount);
77
+ const nodesMap = useTypedSelector(selectNodesMap);
78
+ const usageFilterOptions = useTypedSelector(selectUsageFilterOptions);
79
+
80
+ useEffect(() => {
81
+ dispatch(getNodesList());
82
+
83
+ return () => {
84
+ // Clean data on component unmount
85
+ dispatch(setInitialState());
86
+ };
87
+ }, [dispatch]);
88
+
89
+ const fetchData = useCallback(
90
+ (isBackground: boolean) => {
91
+ if (!isBackground) {
92
+ dispatch(setDataWasNotLoaded());
93
+ }
94
+
95
+ dispatch(
96
+ getStorageInfo(
97
+ {
98
+ tenant,
99
+ nodeId,
100
+ visibleEntities,
101
+ type: storageType,
102
+ },
103
+ {
104
+ concurrentId: 'getStorageInfo',
105
+ },
106
+ ),
107
+ );
108
+ },
109
+ [dispatch, tenant, nodeId, visibleEntities, storageType],
110
+ );
111
+
112
+ const autorefreshEnabled = tenant ? autorefresh : true;
113
+
114
+ useAutofetcher(fetchData, [fetchData], autorefreshEnabled);
115
+
116
+ const handleUsageFilterChange = (value: string[]) => {
117
+ dispatch(setUsageFilter(value));
118
+ };
119
+
120
+ const handleTextFilterChange = (value: string) => {
121
+ dispatch(setStorageTextFilter(value));
122
+ };
123
+
124
+ const handleGroupVisibilityChange = (value: string) => {
125
+ dispatch(setVisibleEntities(value as VisibleEntities));
126
+ };
127
+
128
+ const handleStorageTypeChange = (value: string) => {
129
+ dispatch(setStorageType(value as StorageType));
130
+ };
131
+
132
+ const handleUptimeFilterChange = (value: string) => {
133
+ dispatch(setNodesUptimeFilter(value as NodesUptimeFilterValues));
134
+ };
135
+
136
+ const handleShowAllNodes = () => {
137
+ handleGroupVisibilityChange(VISIBLE_ENTITIES.all);
138
+ handleUptimeFilterChange(NodesUptimeFilterValues.All);
139
+ };
140
+
141
+ const renderDataTable = () => {
142
+ return (
143
+ <>
144
+ {storageType === STORAGE_TYPES.groups && (
145
+ <StorageGroups
146
+ visibleEntities={visibleEntities}
147
+ data={storageGroups}
148
+ tableSettings={tableSettings}
149
+ nodes={nodesMap}
150
+ onShowAll={() => handleGroupVisibilityChange(VISIBLE_ENTITIES.all)}
151
+ />
152
+ )}
153
+ {storageType === STORAGE_TYPES.nodes && (
154
+ <StorageNodes
155
+ visibleEntities={visibleEntities}
156
+ nodesUptimeFilter={nodesUptimeFilter}
157
+ data={storageNodes}
158
+ tableSettings={tableSettings}
159
+ onShowAll={handleShowAllNodes}
160
+ additionalNodesInfo={additionalNodesInfo}
161
+ />
162
+ )}
163
+ </>
164
+ );
165
+ };
166
+
167
+ const renderEntitiesCount = () => {
168
+ const entityName = storageType === STORAGE_TYPES.groups ? 'Groups' : 'Nodes';
169
+ const count = storageType === STORAGE_TYPES.groups ? groupsCount : nodesCount;
170
+ const current =
171
+ storageType === STORAGE_TYPES.groups ? storageGroups.length : storageNodes.length;
172
+
173
+ return (
174
+ <EntitiesCount
175
+ label={entityName}
176
+ loading={loading && !wasLoaded}
177
+ total={count.total}
178
+ current={current}
179
+ />
180
+ );
181
+ };
182
+
183
+ const renderControls = () => {
184
+ return (
185
+ <>
186
+ <div className={b('search')}>
187
+ <Search
188
+ placeholder={
189
+ storageType === STORAGE_TYPES.groups
190
+ ? 'Group ID, Pool name'
191
+ : 'Node ID, FQDN'
192
+ }
193
+ onChange={handleTextFilterChange}
194
+ value={filter}
195
+ />
196
+ </div>
197
+
198
+ <StorageTypeFilter value={storageType} onChange={handleStorageTypeChange} />
199
+ <StorageVisibleEntitiesFilter
200
+ value={visibleEntities}
201
+ onChange={handleGroupVisibilityChange}
202
+ />
203
+
204
+ {storageType === STORAGE_TYPES.nodes && (
205
+ <UptimeFilter value={nodesUptimeFilter} onChange={handleUptimeFilterChange} />
206
+ )}
207
+
208
+ {storageType === STORAGE_TYPES.groups && (
209
+ <UsageFilter
210
+ value={usageFilter}
211
+ onChange={handleUsageFilterChange}
212
+ groups={usageFilterOptions}
213
+ disabled={usageFilterOptions.length === 0}
214
+ />
215
+ )}
216
+ {renderEntitiesCount()}
217
+ </>
218
+ );
219
+ };
220
+
221
+ if (error) {
222
+ if (error.status === 403) {
223
+ return <AccessDenied />;
224
+ }
225
+
226
+ return <ResponseError error={error} />;
227
+ }
228
+
229
+ return (
230
+ <TableWithControlsLayout>
231
+ <TableWithControlsLayout.Controls>{renderControls()}</TableWithControlsLayout.Controls>
232
+ <TableWithControlsLayout.Table loading={loading && !wasLoaded} className={b('table')}>
233
+ {renderDataTable()}
234
+ </TableWithControlsLayout.Table>
235
+ </TableWithControlsLayout>
236
+ );
237
+ };
@@ -1,12 +1,10 @@
1
- import _ from 'lodash';
2
1
  import cn from 'bem-cn-lite';
3
2
 
4
3
  import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
5
4
  import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
6
5
 
7
6
  import type {NodesMap} from '../../../types/store/nodesList';
8
- import type {TVDiskStateInfo} from '../../../types/api/vdisk';
9
- import type {VisibleEntities} from '../../../store/reducers/storage/types';
7
+ import type {PreparedStorageGroup, VisibleEntities} from '../../../store/reducers/storage/types';
10
8
 
11
9
  import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
12
10
  import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
@@ -43,8 +41,8 @@ type TableColumnsIdsKeys = keyof typeof TableColumnsIds;
43
41
  type TableColumnsIdsValues = typeof TableColumnsIds[TableColumnsIdsKeys];
44
42
 
45
43
  interface StorageGroupsProps {
46
- data: any;
47
- nodes: NodesMap;
44
+ data: PreparedStorageGroup[];
45
+ nodes?: NodesMap;
48
46
  tableSettings: Settings;
49
47
  visibleEntities: VisibleEntities;
50
48
  onShowAll?: VoidFunction;
@@ -93,25 +91,25 @@ function setSortOrder(visibleEntities: VisibleEntities): SortOrder | undefined {
93
91
  }
94
92
  }
95
93
 
96
- function StorageGroups({
94
+ export function StorageGroups({
97
95
  data,
98
96
  tableSettings,
99
97
  visibleEntities,
100
98
  nodes,
101
99
  onShowAll,
102
100
  }: StorageGroupsProps) {
103
- const allColumns: Column<any>[] = [
101
+ const allColumns: Column<PreparedStorageGroup>[] = [
104
102
  {
105
103
  name: TableColumnsIds.PoolName,
106
104
  header: tableColumnsNames[TableColumnsIds.PoolName],
107
105
  width: 250,
108
- render: ({value}) => {
109
- const splitted = (value as string)?.split('/');
106
+ render: ({row}) => {
107
+ const splitted = row.PoolName?.split('/');
110
108
  return (
111
109
  <div className={b('pool-name-wrapper')}>
112
110
  {splitted && (
113
111
  <Popover
114
- content={value as string}
112
+ content={row.PoolName}
115
113
  placement={['right']}
116
114
  behavior={PopoverBehavior.Immediate}
117
115
  >
@@ -129,9 +127,9 @@ function StorageGroups({
129
127
  name: TableColumnsIds.Type,
130
128
  header: tableColumnsNames[TableColumnsIds.Type],
131
129
  // prettier-ignore
132
- render: ({value, row}) => (
130
+ render: ({row}) => (
133
131
  <>
134
- <Label>{(value as string) || '—'}</Label>
132
+ <Label>{row.Type || '—'}</Label>
135
133
  {' '}
136
134
  {row.Encryption && (
137
135
  <Popover
@@ -157,8 +155,12 @@ function StorageGroups({
157
155
  name: TableColumnsIds.Missing,
158
156
  header: tableColumnsNames[TableColumnsIds.Missing],
159
157
  width: 100,
160
- render: ({value, row}) =>
161
- value ? <Label theme={getDegradedSeverity(row)}>Degraded: {value}</Label> : '-',
158
+ render: ({row}) =>
159
+ row.Missing ? (
160
+ <Label theme={getDegradedSeverity(row)}>Degraded: {row.Missing}</Label>
161
+ ) : (
162
+ '-'
163
+ ),
162
164
  align: DataTable.LEFT,
163
165
  defaultOrder: DataTable.DESCENDING,
164
166
  },
@@ -189,8 +191,8 @@ function StorageGroups({
189
191
  name: TableColumnsIds.GroupID,
190
192
  header: tableColumnsNames[TableColumnsIds.GroupID],
191
193
  width: 130,
192
- render: ({value}) => {
193
- return <span className={b('group-id')}>{value as number}</span>;
194
+ render: ({row}) => {
195
+ return <span className={b('group-id')}>{row.GroupID}</span>;
194
196
  },
195
197
  align: DataTable.RIGHT,
196
198
  },
@@ -198,8 +200,8 @@ function StorageGroups({
198
200
  name: TableColumnsIds.Used,
199
201
  header: tableColumnsNames[TableColumnsIds.Used],
200
202
  width: 100,
201
- render: ({value}) => {
202
- return bytesToGB(value, true);
203
+ render: ({row}) => {
204
+ return bytesToGB(row.Used, true);
203
205
  },
204
206
  align: DataTable.RIGHT,
205
207
  },
@@ -207,8 +209,8 @@ function StorageGroups({
207
209
  name: TableColumnsIds.Limit,
208
210
  header: tableColumnsNames[TableColumnsIds.Limit],
209
211
  width: 100,
210
- render: ({value}) => {
211
- return bytesToGB(value);
212
+ render: ({row}) => {
213
+ return bytesToGB(row.Limit);
212
214
  },
213
215
  align: DataTable.RIGHT,
214
216
  },
@@ -216,15 +218,17 @@ function StorageGroups({
216
218
  name: TableColumnsIds.UsedSpaceFlag,
217
219
  header: tableColumnsNames[TableColumnsIds.UsedSpaceFlag],
218
220
  width: 110,
219
- render: ({value}) => {
220
- const val = value as number;
221
+ render: ({row}) => {
222
+ const value = row.UsedSpaceFlag;
223
+
221
224
  let color = 'Red';
222
- if (val < 100) {
225
+
226
+ if (value < 100) {
223
227
  color = 'Green';
224
- } else if (val < 10000) {
228
+ } else if (value < 10000) {
225
229
  color = 'Yellow';
226
- } else if (val < 1000000) {
227
- value = 'Orange';
230
+ } else if (value < 1000000) {
231
+ color = 'Orange';
228
232
  }
229
233
  return <EntityStatus status={color} />;
230
234
  },
@@ -235,8 +239,8 @@ function StorageGroups({
235
239
  name: TableColumnsIds.Read,
236
240
  header: tableColumnsNames[TableColumnsIds.Read],
237
241
  width: 100,
238
- render: ({value}) => {
239
- return value ? bytesToSpeed(value) : '-';
242
+ render: ({row}) => {
243
+ return row.Read ? bytesToSpeed(row.Read) : '-';
240
244
  },
241
245
  align: DataTable.RIGHT,
242
246
  },
@@ -244,8 +248,8 @@ function StorageGroups({
244
248
  name: TableColumnsIds.Write,
245
249
  header: tableColumnsNames[TableColumnsIds.Write],
246
250
  width: 100,
247
- render: ({value}) => {
248
- return value ? bytesToSpeed(value) : '-';
251
+ render: ({row}) => {
252
+ return row.Write ? bytesToSpeed(row.Write) : '-';
249
253
  },
250
254
  align: DataTable.RIGHT,
251
255
  },
@@ -253,14 +257,17 @@ function StorageGroups({
253
257
  name: TableColumnsIds.VDisks,
254
258
  className: b('vdisks-column'),
255
259
  header: tableColumnsNames[TableColumnsIds.VDisks],
256
- render: ({value}) => (
260
+ render: ({row}) => (
257
261
  <div className={b('vdisks-wrapper')}>
258
- {_.map(value as TVDiskStateInfo[], (el) => {
259
- const donors = el.Donors;
262
+ {row.VDisks?.map((vDisk) => {
263
+ const donors = vDisk.Donors;
260
264
 
261
265
  return donors && donors.length > 0 ? (
262
- <Stack className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
263
- <VDisk data={el} nodes={nodes} />
266
+ <Stack
267
+ className={b('vdisks-item')}
268
+ key={stringifyVdiskId(vDisk.VDiskId)}
269
+ >
270
+ <VDisk data={vDisk} nodes={nodes} />
264
271
  {donors.map((donor) => {
265
272
  const isFullData = isFullVDiskData(donor);
266
273
 
@@ -277,8 +284,8 @@ function StorageGroups({
277
284
  })}
278
285
  </Stack>
279
286
  ) : (
280
- <div className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
281
- <VDisk data={el} nodes={nodes} />
287
+ <div className={b('vdisks-item')} key={stringifyVdiskId(vDisk.VDiskId)}>
288
+ <VDisk data={vDisk} nodes={nodes} />
282
289
  </div>
283
290
  );
284
291
  })}
@@ -330,7 +337,7 @@ function StorageGroups({
330
337
 
331
338
  return data ? (
332
339
  <DataTable
333
- key={visibleEntities as string}
340
+ key={visibleEntities}
334
341
  theme="yandex-cloud"
335
342
  data={data}
336
343
  columns={columns}
@@ -340,5 +347,3 @@ function StorageGroups({
340
347
  />
341
348
  ) : null;
342
349
  }
343
-
344
- export default StorageGroups;