ydb-embedded-ui 4.27.1 → 4.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/assets/illustrations/dark/error.svg +32 -0
  3. package/dist/assets/illustrations/light/error.svg +32 -0
  4. package/dist/components/EmptyState/EmptyState.scss +5 -2
  5. package/dist/components/EmptyState/EmptyState.tsx +11 -3
  6. package/dist/components/ErrorBoundary/ErrorBoundary.scss +40 -0
  7. package/dist/components/ErrorBoundary/ErrorBoundary.tsx +62 -0
  8. package/dist/components/ErrorBoundary/i18n/en.json +7 -0
  9. package/dist/components/ErrorBoundary/i18n/index.ts +11 -0
  10. package/dist/components/ErrorBoundary/i18n/ru.json +7 -0
  11. package/dist/components/Errors/403/AccessDenied.tsx +4 -3
  12. package/dist/components/Illustration/Illustration.tsx +3 -1
  13. package/dist/components/ProblemFilter/ProblemFilter.tsx +1 -1
  14. package/dist/components/UptimeFIlter/UptimeFilter.tsx +1 -1
  15. package/dist/components/VirtualTable/VirtualTable.scss +1 -1
  16. package/dist/containers/App/App.js +5 -2
  17. package/dist/containers/Cluster/Cluster.tsx +11 -12
  18. package/dist/containers/Nodes/Nodes.tsx +4 -4
  19. package/dist/containers/Nodes/NodesWrapper.tsx +21 -0
  20. package/dist/containers/Nodes/VirtualNodes.tsx +4 -14
  21. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +1 -0
  22. package/dist/containers/Storage/EmptyFilter/EmptyFilter.tsx +1 -0
  23. package/dist/containers/Storage/PDisk/PDisk.tsx +5 -7
  24. package/dist/containers/Storage/Storage.tsx +30 -67
  25. package/dist/containers/Storage/StorageControls/StorageControls.tsx +104 -0
  26. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +18 -62
  27. package/dist/containers/Storage/StorageGroups/StorageGroupsEmptyDataMessage.tsx +30 -0
  28. package/dist/containers/Storage/StorageGroups/VirtualStorageGroups.tsx +94 -0
  29. package/dist/containers/Storage/StorageGroups/getGroups.ts +21 -0
  30. package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +73 -50
  31. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +23 -138
  32. package/dist/containers/Storage/StorageNodes/StorageNodesEmptyDataMessage.tsx +44 -0
  33. package/dist/containers/Storage/StorageNodes/VirtualStorageNodes.tsx +105 -0
  34. package/dist/containers/Storage/StorageNodes/getNodes.ts +26 -0
  35. package/dist/containers/Storage/StorageNodes/getStorageNodesColumns.tsx +125 -0
  36. package/dist/containers/Storage/StorageNodes/shared.ts +9 -0
  37. package/dist/containers/Storage/StorageTypeFilter/StorageTypeFilter.tsx +1 -1
  38. package/dist/containers/Storage/StorageVisibleEntitiesFilter/StorageVisibleEntitiesFilter.tsx +1 -1
  39. package/dist/containers/Storage/StorageWrapper.tsx +23 -0
  40. package/dist/containers/Storage/UsageFilter/UsageFilter.tsx +3 -4
  41. package/dist/containers/Storage/VirtualStorage.tsx +112 -0
  42. package/dist/containers/Storage/i18n/en.json +7 -0
  43. package/dist/containers/Storage/i18n/index.ts +11 -0
  44. package/dist/containers/Storage/i18n/ru.json +7 -0
  45. package/dist/containers/Storage/shared.ts +3 -0
  46. package/dist/containers/Tenants/Tenants.tsx +2 -2
  47. package/dist/containers/UserSettings/i18n/en.json +2 -2
  48. package/dist/containers/UserSettings/i18n/ru.json +2 -2
  49. package/dist/containers/UserSettings/settings.ts +4 -4
  50. package/dist/index.tsx +8 -5
  51. package/dist/store/reducers/storage/selectors.ts +0 -20
  52. package/dist/utils/registerError.ts +18 -0
  53. package/package.json +7 -6
@@ -1,13 +1,14 @@
1
1
  import cn from 'bem-cn-lite';
2
2
 
3
- import DataTable, {type Column} from '@gravity-ui/react-data-table';
3
+ import DataTable, {type Column as DataTableColumn} from '@gravity-ui/react-data-table';
4
4
  import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
5
5
 
6
+ import type {Column as VirtualTableColumn} from '../../../components/VirtualTable';
6
7
  import shieldIcon from '../../../assets/icons/shield.svg';
7
- import type {ValueOf} from '../../../types/common';
8
8
  import type {NodesMap} from '../../../types/store/nodesList';
9
- import type {PreparedStorageGroup} from '../../../store/reducers/storage/types';
10
- import {getUsage, isFullVDiskData} from '../../../utils/storage';
9
+ import type {PreparedStorageGroup, VisibleEntities} from '../../../store/reducers/storage/types';
10
+ import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
11
+ import {getUsage, isFullVDiskData, isSortableStorageProperty} from '../../../utils/storage';
11
12
  import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
12
13
  import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters';
13
14
  import EntityStatus from '../../../components/EntityStatus/EntityStatus';
@@ -21,7 +22,10 @@ import './StorageGroups.scss';
21
22
 
22
23
  const b = cn('global-storage-groups');
23
24
 
24
- const GROUPS_COLUMNS_IDS = {
25
+ type StorageGroupsColumn = VirtualTableColumn<PreparedStorageGroup> &
26
+ DataTableColumn<PreparedStorageGroup>;
27
+
28
+ export const GROUPS_COLUMNS_IDS = {
25
29
  PoolName: 'PoolName',
26
30
  Kind: 'Kind',
27
31
  Erasure: 'Erasure',
@@ -36,26 +40,9 @@ const GROUPS_COLUMNS_IDS = {
36
40
  Degraded: 'Degraded',
37
41
  } as const;
38
42
 
39
- type GroupsColumns = ValueOf<typeof GROUPS_COLUMNS_IDS>;
40
-
41
- const tableColumnsNames: Record<GroupsColumns, string> = {
42
- PoolName: 'Pool Name',
43
- Kind: 'Type',
44
- Erasure: 'Erasure',
45
- GroupId: 'Group ID',
46
- Used: 'Used',
47
- Limit: 'Limit',
48
- UsedSpaceFlag: 'Space',
49
- Usage: 'Usage',
50
- Read: 'Read',
51
- Write: 'Write',
52
- VDisks: 'VDisks',
53
- Degraded: 'Degraded',
54
- };
55
-
56
- const poolNameColumn: Column<PreparedStorageGroup> = {
43
+ const poolNameColumn: StorageGroupsColumn = {
57
44
  name: GROUPS_COLUMNS_IDS.PoolName,
58
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.PoolName],
45
+ header: 'Pool Name',
59
46
  width: 250,
60
47
  render: ({row}) => {
61
48
  const splitted = row.PoolName?.split('/');
@@ -75,14 +62,15 @@ const poolNameColumn: Column<PreparedStorageGroup> = {
75
62
  align: DataTable.LEFT,
76
63
  };
77
64
 
78
- const kindColumn: Column<PreparedStorageGroup> = {
65
+ const kindColumn: StorageGroupsColumn = {
79
66
  name: GROUPS_COLUMNS_IDS.Kind,
80
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Kind],
81
- // prettier-ignore
67
+ header: 'Type',
68
+ width: 100,
69
+ align: DataTable.LEFT,
82
70
  render: ({row}) => (
83
71
  <>
84
72
  <Label>{row.Kind || '—'}</Label>
85
- {' '}
73
+ {'\u00a0'}
86
74
  {row.Encryption && (
87
75
  <Popover
88
76
  content={i18n('encrypted')}
@@ -99,17 +87,18 @@ const kindColumn: Column<PreparedStorageGroup> = {
99
87
  sortable: false,
100
88
  };
101
89
 
102
- const erasureColumn: Column<PreparedStorageGroup> = {
90
+ const erasureColumn: StorageGroupsColumn = {
103
91
  name: GROUPS_COLUMNS_IDS.Erasure,
104
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Erasure],
92
+ header: 'Erasure',
93
+ width: 100,
105
94
  render: ({row}) => (row.ErasureSpecies ? row.ErasureSpecies : '-'),
106
95
  align: DataTable.LEFT,
107
96
  sortable: false,
108
97
  };
109
98
 
110
- const degradedColumn: Column<PreparedStorageGroup> = {
99
+ const degradedColumn: StorageGroupsColumn = {
111
100
  name: GROUPS_COLUMNS_IDS.Degraded,
112
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Degraded],
101
+ header: 'Degraded',
113
102
  width: 100,
114
103
  render: ({row}) =>
115
104
  row.Degraded ? (
@@ -121,9 +110,9 @@ const degradedColumn: Column<PreparedStorageGroup> = {
121
110
  defaultOrder: DataTable.DESCENDING,
122
111
  };
123
112
 
124
- const usageColumn: Column<PreparedStorageGroup> = {
113
+ const usageColumn: StorageGroupsColumn = {
125
114
  name: GROUPS_COLUMNS_IDS.Usage,
126
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Usage],
115
+ header: 'Usage',
127
116
  width: 100,
128
117
  render: ({row}) => {
129
118
  // without a limit the usage can be evaluated as 0,
@@ -140,9 +129,9 @@ const usageColumn: Column<PreparedStorageGroup> = {
140
129
  sortable: false,
141
130
  };
142
131
 
143
- const groupIdColumn: Column<PreparedStorageGroup> = {
132
+ const groupIdColumn: StorageGroupsColumn = {
144
133
  name: GROUPS_COLUMNS_IDS.GroupId,
145
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.GroupId],
134
+ header: 'Group ID',
146
135
  width: 130,
147
136
  render: ({row}) => {
148
137
  return <span className={b('group-id')}>{row.GroupID}</span>;
@@ -152,9 +141,9 @@ const groupIdColumn: Column<PreparedStorageGroup> = {
152
141
  sortable: false,
153
142
  };
154
143
 
155
- const usedColumn: Column<PreparedStorageGroup> = {
144
+ const usedColumn: StorageGroupsColumn = {
156
145
  name: GROUPS_COLUMNS_IDS.Used,
157
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Used],
146
+ header: 'Used',
158
147
  width: 100,
159
148
  render: ({row}) => {
160
149
  return bytesToGB(row.Used, true);
@@ -163,9 +152,9 @@ const usedColumn: Column<PreparedStorageGroup> = {
163
152
  sortable: false,
164
153
  };
165
154
 
166
- const limitColumn: Column<PreparedStorageGroup> = {
155
+ const limitColumn: StorageGroupsColumn = {
167
156
  name: GROUPS_COLUMNS_IDS.Limit,
168
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Limit],
157
+ header: 'Limit',
169
158
  width: 100,
170
159
  render: ({row}) => {
171
160
  return bytesToGB(row.Limit);
@@ -174,9 +163,9 @@ const limitColumn: Column<PreparedStorageGroup> = {
174
163
  sortable: false,
175
164
  };
176
165
 
177
- const usedSpaceFlagColumn: Column<PreparedStorageGroup> = {
166
+ const usedSpaceFlagColumn: StorageGroupsColumn = {
178
167
  name: GROUPS_COLUMNS_IDS.UsedSpaceFlag,
179
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.UsedSpaceFlag],
168
+ header: 'Space',
180
169
  width: 110,
181
170
  render: ({row}) => {
182
171
  const value = row.UsedSpaceFlag;
@@ -195,9 +184,9 @@ const usedSpaceFlagColumn: Column<PreparedStorageGroup> = {
195
184
  align: DataTable.CENTER,
196
185
  };
197
186
 
198
- const readColumn: Column<PreparedStorageGroup> = {
187
+ const readColumn: StorageGroupsColumn = {
199
188
  name: GROUPS_COLUMNS_IDS.Read,
200
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Read],
189
+ header: 'Read',
201
190
  width: 100,
202
191
  render: ({row}) => {
203
192
  return row.Read ? bytesToSpeed(row.Read) : '-';
@@ -205,9 +194,9 @@ const readColumn: Column<PreparedStorageGroup> = {
205
194
  align: DataTable.RIGHT,
206
195
  };
207
196
 
208
- const writeColumn: Column<PreparedStorageGroup> = {
197
+ const writeColumn: StorageGroupsColumn = {
209
198
  name: GROUPS_COLUMNS_IDS.Write,
210
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.Write],
199
+ header: 'Write',
211
200
  width: 100,
212
201
  render: ({row}) => {
213
202
  return row.Write ? bytesToSpeed(row.Write) : '-';
@@ -215,10 +204,10 @@ const writeColumn: Column<PreparedStorageGroup> = {
215
204
  align: DataTable.RIGHT,
216
205
  };
217
206
 
218
- const getVdiscksColumn = (nodes?: NodesMap): Column<PreparedStorageGroup> => ({
207
+ const getVdiscksColumn = (nodes?: NodesMap): StorageGroupsColumn => ({
219
208
  name: GROUPS_COLUMNS_IDS.VDisks,
220
209
  className: b('vdisks-column'),
221
- header: tableColumnsNames[GROUPS_COLUMNS_IDS.VDisks],
210
+ header: 'VDisks',
222
211
  render: ({row}) => (
223
212
  <div className={b('vdisks-wrapper')}>
224
213
  {row.VDisks?.map((vDisk) => {
@@ -252,11 +241,11 @@ const getVdiscksColumn = (nodes?: NodesMap): Column<PreparedStorageGroup> => ({
252
241
  width: 900,
253
242
  });
254
243
 
255
- export const getStorageTopGroupsColumns = (): Column<PreparedStorageGroup>[] => {
244
+ export const getStorageTopGroupsColumns = (): StorageGroupsColumn[] => {
256
245
  return [groupIdColumn, kindColumn, erasureColumn, usageColumn, usedColumn, limitColumn];
257
246
  };
258
247
 
259
- export const getStorageGroupsColumns = (nodes?: NodesMap): Column<PreparedStorageGroup>[] => {
248
+ const getStorageGroupsColumns = (nodes?: NodesMap): StorageGroupsColumn[] => {
260
249
  return [
261
250
  poolNameColumn,
262
251
  kindColumn,
@@ -272,3 +261,37 @@ export const getStorageGroupsColumns = (nodes?: NodesMap): Column<PreparedStorag
272
261
  getVdiscksColumn(nodes),
273
262
  ];
274
263
  };
264
+
265
+ const filterStorageGroupsColumns = (
266
+ columns: StorageGroupsColumn[],
267
+ visibleEntities: VisibleEntities,
268
+ ) => {
269
+ if (visibleEntities === VISIBLE_ENTITIES.space) {
270
+ return columns.filter((col) => col.name !== GROUPS_COLUMNS_IDS.Degraded);
271
+ }
272
+
273
+ if (visibleEntities === VISIBLE_ENTITIES.missing) {
274
+ return columns.filter((col) => col.name !== GROUPS_COLUMNS_IDS.UsedSpaceFlag);
275
+ }
276
+
277
+ return columns.filter((col) => {
278
+ return (
279
+ col.name !== GROUPS_COLUMNS_IDS.Degraded &&
280
+ col.name !== GROUPS_COLUMNS_IDS.UsedSpaceFlag
281
+ );
282
+ });
283
+ };
284
+
285
+ export const getPreparedStorageGroupsColumns = (
286
+ nodesMap: NodesMap | undefined,
287
+ visibleEntities: VisibleEntities,
288
+ ) => {
289
+ const rawColumns = getStorageGroupsColumns(nodesMap);
290
+
291
+ const filteredColumns = filterStorageGroupsColumns(rawColumns, visibleEntities);
292
+
293
+ return filteredColumns.map((column) => ({
294
+ ...column,
295
+ sortable: isSortableStorageProperty(column.name),
296
+ }));
297
+ };
@@ -1,62 +1,30 @@
1
- import cn from 'bem-cn-lite';
1
+ import DataTable, {Settings, SortOrder} from '@gravity-ui/react-data-table';
2
2
 
3
- import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
4
-
5
- import type {ValueOf} from '../../../types/common';
6
3
  import type {PreparedStorageNode, VisibleEntities} from '../../../store/reducers/storage/types';
7
4
  import type {HandleSort} from '../../../utils/hooks/useTableSort';
8
5
  import type {AdditionalNodesProps} from '../../../types/additionalProps';
9
6
 
10
7
  import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
11
- import {
12
- isSortableNodesProperty,
13
- isUnavailableNode,
14
- NodesUptimeFilterValues,
15
- } from '../../../utils/nodes';
16
-
17
- import {NodeHostWrapper} from '../../../components/NodeHostWrapper/NodeHostWrapper';
8
+ import {NodesUptimeFilterValues} from '../../../utils/nodes';
18
9
 
19
- import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
20
- import {PDisk} from '../PDisk';
10
+ import {getPreparedStorageNodesColumns} from './getStorageNodesColumns';
21
11
 
12
+ import {StorageNodesEmptyDataMessage} from './StorageNodesEmptyDataMessage';
13
+ import {getRowUnavailableClassName} from './shared';
22
14
  import i18n from './i18n';
23
15
  import './StorageNodes.scss';
24
16
 
25
- const TableColumnsIds = {
26
- NodeId: 'NodeId',
27
- Host: 'Host',
28
- DC: 'DC',
29
- Rack: 'Rack',
30
- Uptime: 'Uptime',
31
- PDisks: 'PDisks',
32
- Missing: 'Missing',
33
- } as const;
34
-
35
- type TableColumnId = ValueOf<typeof TableColumnsIds>;
36
-
37
17
  interface StorageNodesProps {
38
18
  data: PreparedStorageNode[];
39
19
  tableSettings: Settings;
40
20
  visibleEntities: VisibleEntities;
41
- nodesUptimeFilter: keyof typeof NodesUptimeFilterValues;
21
+ nodesUptimeFilter: NodesUptimeFilterValues;
42
22
  onShowAll?: VoidFunction;
43
23
  additionalNodesProps?: AdditionalNodesProps;
44
24
  sort?: SortOrder;
45
25
  handleSort?: HandleSort;
46
26
  }
47
27
 
48
- const tableColumnsNames: Record<TableColumnId, string> = {
49
- NodeId: 'Node ID',
50
- Host: 'Host',
51
- DC: 'DC',
52
- Rack: 'Rack',
53
- Uptime: 'Uptime',
54
- PDisks: 'PDisks',
55
- Missing: 'Missing',
56
- };
57
-
58
- const b = cn('global-storage-nodes');
59
-
60
28
  export function StorageNodes({
61
29
  data,
62
30
  tableSettings,
@@ -67,106 +35,23 @@ export function StorageNodes({
67
35
  sort,
68
36
  handleSort,
69
37
  }: StorageNodesProps) {
70
- const getNodeRef = additionalNodesProps?.getNodeRef;
71
-
72
- const rawColumns: Column<PreparedStorageNode>[] = [
73
- {
74
- name: TableColumnsIds.NodeId,
75
- header: tableColumnsNames[TableColumnsIds.NodeId],
76
- width: 100,
77
- align: DataTable.RIGHT,
78
- },
79
- {
80
- name: TableColumnsIds.Host,
81
- header: tableColumnsNames[TableColumnsIds.Host],
82
- width: 350,
83
- render: ({row}) => {
84
- return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
85
- },
86
- align: DataTable.LEFT,
87
- },
88
- {
89
- name: TableColumnsIds.DC,
90
- header: tableColumnsNames[TableColumnsIds.DC],
91
- render: ({row}) => row.DataCenter || '—',
92
- align: DataTable.LEFT,
93
- },
94
- {
95
- name: TableColumnsIds.Rack,
96
- header: tableColumnsNames[TableColumnsIds.Rack],
97
- render: ({row}) => row.Rack || '—',
98
- align: DataTable.LEFT,
99
- },
100
- {
101
- name: TableColumnsIds.Uptime,
102
- header: tableColumnsNames[TableColumnsIds.Uptime],
103
- width: 130,
104
- sortAccessor: ({StartTime}) => (StartTime ? -StartTime : 0),
105
- align: DataTable.RIGHT,
106
- },
107
- {
108
- name: TableColumnsIds.Missing,
109
- header: tableColumnsNames[TableColumnsIds.Missing],
110
- width: 100,
111
- align: DataTable.CENTER,
112
- defaultOrder: DataTable.DESCENDING,
113
- },
114
- {
115
- name: TableColumnsIds.PDisks,
116
- className: b('pdisks-column'),
117
- header: tableColumnsNames[TableColumnsIds.PDisks],
118
- render: ({row}) => (
119
- <div className={b('pdisks-wrapper')}>
120
- {row.PDisks?.map((pDisk) => (
121
- <div className={b('pdisks-item')} key={pDisk.PDiskId}>
122
- <PDisk data={pDisk} nodeId={row.NodeId} />
123
- </div>
124
- ))}
125
- </div>
126
- ),
127
- align: DataTable.CENTER,
128
- sortable: false,
129
- width: 900,
130
- },
131
- ];
132
-
133
- let columns = rawColumns.map((column) => ({
134
- ...column,
135
- sortable: isSortableNodesProperty(column.name),
136
- }));
137
-
138
- if (visibleEntities !== VISIBLE_ENTITIES.missing) {
139
- columns = columns.filter((col) => col.name !== TableColumnsIds.Missing);
140
- }
141
-
142
- if (!data.length) {
143
- let message;
144
-
145
- if (visibleEntities === VISIBLE_ENTITIES.space) {
146
- message = i18n('empty.out_of_space');
147
- }
148
-
149
- if (visibleEntities === VISIBLE_ENTITIES.missing) {
150
- message = i18n('empty.degraded');
151
- }
152
-
153
- if (nodesUptimeFilter === NodesUptimeFilterValues.SmallUptime) {
154
- message = i18n('empty.small_uptime');
155
- }
156
-
157
- if (
158
- visibleEntities !== VISIBLE_ENTITIES.all &&
159
- nodesUptimeFilter !== NodesUptimeFilterValues.All
160
- ) {
161
- message = i18n('empty.several_filters');
162
- }
163
-
164
- if (message) {
165
- return <EmptyFilter title={message} showAll={i18n('show_all')} onShowAll={onShowAll} />;
166
- }
38
+ const columns = getPreparedStorageNodesColumns(additionalNodesProps, visibleEntities);
39
+
40
+ if (
41
+ !data.length &&
42
+ (visibleEntities !== VISIBLE_ENTITIES.all ||
43
+ nodesUptimeFilter !== NodesUptimeFilterValues.All)
44
+ ) {
45
+ return (
46
+ <StorageNodesEmptyDataMessage
47
+ visibleEntities={visibleEntities}
48
+ nodesUptimeFilter={nodesUptimeFilter}
49
+ onShowAll={onShowAll}
50
+ />
51
+ );
167
52
  }
168
53
 
169
- return data ? (
54
+ return (
170
55
  <DataTable
171
56
  key={visibleEntities as string}
172
57
  theme="yandex-cloud"
@@ -177,9 +62,9 @@ export function StorageNodes({
177
62
  dynamicRenderType: 'variable',
178
63
  }}
179
64
  emptyDataMessage={i18n('empty.default')}
180
- rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
65
+ rowClassName={getRowUnavailableClassName}
181
66
  sortOrder={sort}
182
67
  onSort={handleSort}
183
68
  />
184
- ) : null;
69
+ );
185
70
  }
@@ -0,0 +1,44 @@
1
+ import type {VisibleEntities} from '../../../store/reducers/storage/types';
2
+ import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
3
+ import {NodesUptimeFilterValues} from '../../../utils/nodes';
4
+ import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
5
+ import i18n from './i18n';
6
+
7
+ interface StorageNodesEmptyDataMessageProps {
8
+ visibleEntities: VisibleEntities;
9
+ nodesUptimeFilter: NodesUptimeFilterValues;
10
+ onShowAll?: VoidFunction;
11
+ }
12
+
13
+ export const StorageNodesEmptyDataMessage = ({
14
+ visibleEntities,
15
+ nodesUptimeFilter,
16
+ onShowAll,
17
+ }: StorageNodesEmptyDataMessageProps) => {
18
+ let message;
19
+
20
+ if (visibleEntities === VISIBLE_ENTITIES.space) {
21
+ message = i18n('empty.out_of_space');
22
+ }
23
+
24
+ if (visibleEntities === VISIBLE_ENTITIES.missing) {
25
+ message = i18n('empty.degraded');
26
+ }
27
+
28
+ if (nodesUptimeFilter === NodesUptimeFilterValues.SmallUptime) {
29
+ message = i18n('empty.small_uptime');
30
+ }
31
+
32
+ if (
33
+ visibleEntities !== VISIBLE_ENTITIES.all &&
34
+ nodesUptimeFilter !== NodesUptimeFilterValues.All
35
+ ) {
36
+ message = i18n('empty.several_filters');
37
+ }
38
+
39
+ if (message) {
40
+ return <EmptyFilter title={message} showAll={i18n('show_all')} onShowAll={onShowAll} />;
41
+ }
42
+
43
+ return null;
44
+ };
@@ -0,0 +1,105 @@
1
+ import {useCallback, useMemo} from 'react';
2
+
3
+ import type {AdditionalNodesProps} from '../../../types/additionalProps';
4
+ import {
5
+ getUptimeParamValue,
6
+ NodesUptimeFilterValues,
7
+ type NodesSortValue,
8
+ } from '../../../utils/nodes';
9
+ import {
10
+ VirtualTable,
11
+ type FetchData,
12
+ type RenderControls,
13
+ type RenderErrorMessage,
14
+ } from '../../../components/VirtualTable';
15
+ import type {PreparedStorageNode, VisibleEntities} from '../../../store/reducers/storage/types';
16
+ import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
17
+
18
+ import {StorageNodesEmptyDataMessage} from './StorageNodesEmptyDataMessage';
19
+ import {getPreparedStorageNodesColumns} from './getStorageNodesColumns';
20
+ import {getStorageNodes} from './getNodes';
21
+ import {getRowUnavailableClassName} from './shared';
22
+ import i18n from './i18n';
23
+
24
+ interface VirtualStorageNodesProps {
25
+ searchValue: string;
26
+ visibleEntities: VisibleEntities;
27
+ nodesUptimeFilter: NodesUptimeFilterValues;
28
+ tenant?: string;
29
+
30
+ additionalNodesProps?: AdditionalNodesProps;
31
+ onShowAll: VoidFunction;
32
+
33
+ parentContainer?: Element | null;
34
+ renderControls: RenderControls;
35
+ renderErrorMessage: RenderErrorMessage;
36
+ }
37
+
38
+ export const VirtualStorageNodes = ({
39
+ searchValue,
40
+ visibleEntities,
41
+ nodesUptimeFilter,
42
+ tenant,
43
+ additionalNodesProps,
44
+ onShowAll,
45
+ parentContainer,
46
+ renderControls,
47
+ renderErrorMessage,
48
+ }: VirtualStorageNodesProps) => {
49
+ const filters = useMemo(() => {
50
+ return [searchValue, visibleEntities, nodesUptimeFilter, tenant];
51
+ }, [searchValue, visibleEntities, nodesUptimeFilter, tenant]);
52
+
53
+ const fetchData = useCallback<FetchData<PreparedStorageNode>>(
54
+ async (limit, offset, {sortOrder, columnId} = {}) => {
55
+ return await getStorageNodes({
56
+ limit,
57
+ offset,
58
+ filter: searchValue,
59
+ uptime: getUptimeParamValue(nodesUptimeFilter),
60
+ visibleEntities,
61
+ tenant,
62
+
63
+ sortOrder,
64
+ sortValue: columnId as NodesSortValue,
65
+ });
66
+ },
67
+ [nodesUptimeFilter, searchValue, tenant, visibleEntities],
68
+ );
69
+
70
+ const columns = useMemo(() => {
71
+ return getPreparedStorageNodesColumns(additionalNodesProps, visibleEntities);
72
+ }, [additionalNodesProps, visibleEntities]);
73
+
74
+ const renderEmptyDataMessage = () => {
75
+ if (
76
+ visibleEntities !== VISIBLE_ENTITIES.all ||
77
+ nodesUptimeFilter !== NodesUptimeFilterValues.All
78
+ ) {
79
+ return (
80
+ <StorageNodesEmptyDataMessage
81
+ onShowAll={onShowAll}
82
+ nodesUptimeFilter={nodesUptimeFilter}
83
+ visibleEntities={visibleEntities}
84
+ />
85
+ );
86
+ }
87
+
88
+ return i18n('empty.default');
89
+ };
90
+
91
+ return (
92
+ <VirtualTable
93
+ parentContainer={parentContainer}
94
+ columns={columns}
95
+ fetchData={fetchData}
96
+ rowHeight={50}
97
+ limit={50}
98
+ renderControls={renderControls}
99
+ renderErrorMessage={renderErrorMessage}
100
+ renderEmptyDataMessage={renderEmptyDataMessage}
101
+ getRowClassName={getRowUnavailableClassName}
102
+ dependencyArray={filters}
103
+ />
104
+ );
105
+ };
@@ -0,0 +1,26 @@
1
+ import type {NodesApiRequestParams} from '../../../store/reducers/nodes/types';
2
+ import {prepareStorageNodesResponse} from '../../../store/reducers/storage/utils';
3
+
4
+ const getConcurrentId = (limit?: number, offset?: number) => {
5
+ return `getStorageNodes|offset${offset}|limit${limit}`;
6
+ };
7
+
8
+ export const getStorageNodes = async ({
9
+ type = 'static',
10
+ storage = true,
11
+ limit,
12
+ offset,
13
+ ...params
14
+ }: NodesApiRequestParams) => {
15
+ const response = await window.api.getNodes(
16
+ {type, storage, limit, offset, ...params},
17
+ {concurrentId: getConcurrentId(limit, offset)},
18
+ );
19
+ const preparedResponse = prepareStorageNodesResponse(response);
20
+
21
+ return {
22
+ data: preparedResponse.nodes || [],
23
+ found: preparedResponse.found || 0,
24
+ total: preparedResponse.total || 0,
25
+ };
26
+ };