ydb-embedded-ui 4.27.1 → 4.29.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 (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
+ };