ydb-embedded-ui 4.13.0 → 4.15.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 (71) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/components/Tablet/Tablet.scss +1 -16
  3. package/dist/components/Tablet/Tablet.tsx +5 -5
  4. package/dist/components/TabletIcon/TabletIcon.scss +17 -0
  5. package/dist/components/TabletIcon/TabletIcon.tsx +18 -0
  6. package/dist/containers/App/App.js +1 -1
  7. package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
  8. package/dist/containers/Authentication/Authentication.tsx +1 -1
  9. package/dist/containers/Header/Header.scss +2 -0
  10. package/dist/containers/Header/Header.tsx +2 -7
  11. package/dist/containers/Header/{breadcrumbs.ts → breadcrumbs.tsx} +19 -8
  12. package/dist/containers/Nodes/Nodes.tsx +53 -16
  13. package/dist/containers/Nodes/getNodesColumns.tsx +31 -13
  14. package/dist/containers/Storage/Storage.tsx +64 -32
  15. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +56 -73
  16. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +33 -43
  17. package/dist/containers/Storage/utils/index.ts +3 -3
  18. package/dist/containers/Tablet/Tablet.tsx +9 -3
  19. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.scss +8 -0
  20. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +13 -1
  21. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.scss +3 -1
  22. package/dist/containers/Tenant/Query/i18n/en.json +6 -4
  23. package/dist/containers/Tenant/Query/i18n/ru.json +6 -4
  24. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +4 -0
  25. package/dist/containers/Tenant/i18n/en.json +3 -0
  26. package/dist/containers/Tenant/i18n/ru.json +3 -0
  27. package/dist/containers/Tenant/utils/queryTemplates.ts +89 -0
  28. package/dist/containers/Tenant/utils/schema.ts +1 -1
  29. package/dist/containers/Tenant/utils/schemaActions.ts +30 -54
  30. package/dist/containers/Tenant/utils/schemaControls.tsx +69 -0
  31. package/dist/containers/UserSettings/i18n/en.json +3 -0
  32. package/dist/containers/UserSettings/i18n/ru.json +3 -0
  33. package/dist/containers/UserSettings/settings.ts +12 -1
  34. package/dist/{reportWebVitals.js → reportWebVitals.ts} +3 -1
  35. package/dist/services/api.ts +30 -16
  36. package/dist/store/reducers/{authentication.js → authentication/authentication.ts} +14 -7
  37. package/dist/store/reducers/authentication/types.ts +15 -0
  38. package/dist/store/reducers/describe.ts +1 -16
  39. package/dist/store/reducers/header/types.ts +2 -0
  40. package/dist/store/reducers/index.ts +1 -1
  41. package/dist/store/reducers/nodes/nodes.ts +23 -6
  42. package/dist/store/reducers/nodes/selectors.ts +2 -2
  43. package/dist/store/reducers/nodes/types.ts +15 -5
  44. package/dist/store/reducers/settings/settings.ts +5 -0
  45. package/dist/store/reducers/storage/selectors.ts +50 -150
  46. package/dist/store/reducers/storage/storage.ts +73 -25
  47. package/dist/store/reducers/storage/types.ts +49 -17
  48. package/dist/store/reducers/storage/utils.ts +207 -0
  49. package/dist/store/utils.ts +1 -1
  50. package/dist/types/api/compute.ts +0 -12
  51. package/dist/types/api/error.ts +4 -0
  52. package/dist/types/api/nodes.ts +0 -12
  53. package/dist/types/api/storage.ts +32 -4
  54. package/dist/types/window.d.ts +1 -0
  55. package/dist/utils/constants.ts +3 -0
  56. package/dist/utils/filters.ts +23 -0
  57. package/dist/utils/hooks/index.ts +4 -0
  58. package/dist/utils/hooks/useNodesRequestParams.ts +46 -0
  59. package/dist/utils/hooks/useStorageRequestParams.ts +28 -0
  60. package/dist/utils/hooks/useTableSort.ts +37 -0
  61. package/dist/utils/nodes.ts +25 -0
  62. package/dist/utils/storage.ts +31 -3
  63. package/package.json +2 -6
  64. package/dist/HOCS/WithSearch/WithSearch.js +0 -26
  65. package/dist/HOCS/index.js +0 -1
  66. package/dist/components/Hotkey/Hotkey.js +0 -102
  67. package/dist/components/Pagination/Pagination.js +0 -63
  68. package/dist/components/Pagination/Pagination.scss +0 -28
  69. package/dist/types/store/storage.ts +0 -12
  70. /package/dist/{index.js → index.tsx} +0 -0
  71. /package/dist/utils/{monaco.js → monaco.ts} +0 -0
@@ -2,8 +2,6 @@ import {useCallback, useEffect} from 'react';
2
2
  import {useDispatch} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
 
5
- import DataTable, {Settings} from '@gravity-ui/react-data-table';
6
-
7
5
  import {Search} from '../../components/Search';
8
6
  import {UptimeFilter} from '../../components/UptimeFIlter';
9
7
  import {AccessDenied} from '../../components/Errors/403';
@@ -11,9 +9,13 @@ import {EntitiesCount} from '../../components/EntitiesCount';
11
9
  import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
12
10
  import {ResponseError} from '../../components/Errors/ResponseError';
13
11
 
14
- import type {StorageType, VisibleEntities} from '../../store/reducers/storage/types';
12
+ import type {
13
+ StorageSortParams,
14
+ StorageType,
15
+ VisibleEntities,
16
+ } from '../../store/reducers/storage/types';
17
+ import type {NodesSortParams} from '../../store/reducers/nodes/types';
15
18
  import {
16
- getStorageInfo,
17
19
  setInitialState,
18
20
  setVisibleEntities,
19
21
  setStorageTextFilter,
@@ -21,17 +23,28 @@ import {
21
23
  setStorageType,
22
24
  setNodesUptimeFilter,
23
25
  setDataWasNotLoaded,
26
+ getStorageNodesInfo,
27
+ getStorageGroupsInfo,
28
+ setNodesSortParams,
29
+ setGroupsSortParams,
24
30
  } from '../../store/reducers/storage/storage';
25
31
  import {
26
32
  selectFilteredGroups,
27
33
  selectFilteredNodes,
28
- selectStorageNodesCount,
29
- selectStorageGroupsCount,
34
+ selectEntitiesCount,
30
35
  selectUsageFilterOptions,
36
+ selectNodesSortParams,
37
+ selectGroupsSortParams,
31
38
  } from '../../store/reducers/storage/selectors';
32
39
  import {VISIBLE_ENTITIES, STORAGE_TYPES} from '../../store/reducers/storage/constants';
33
40
  import {getNodesList, selectNodesMap} from '../../store/reducers/nodesList';
34
- import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
41
+ import {
42
+ useAutofetcher,
43
+ useNodesRequestParams,
44
+ useStorageRequestParams,
45
+ useTableSort,
46
+ useTypedSelector,
47
+ } from '../../utils/hooks';
35
48
  import {AdditionalNodesInfo, NodesUptimeFilterValues} from '../../utils/nodes';
36
49
  import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
37
50
 
@@ -45,11 +58,6 @@ import './Storage.scss';
45
58
 
46
59
  const b = cn('global-storage');
47
60
 
48
- const tableSettings: Settings = {
49
- ...DEFAULT_TABLE_SETTINGS,
50
- defaultOrder: DataTable.DESCENDING,
51
- };
52
-
53
61
  interface StorageProps {
54
62
  additionalNodesInfo?: AdditionalNodesInfo;
55
63
  tenant?: string;
@@ -72,10 +80,11 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
72
80
  } = useTypedSelector((state) => state.storage);
73
81
  const storageNodes = useTypedSelector(selectFilteredNodes);
74
82
  const storageGroups = useTypedSelector(selectFilteredGroups);
75
- const nodesCount = useTypedSelector(selectStorageNodesCount);
76
- const groupsCount = useTypedSelector(selectStorageGroupsCount);
83
+ const entitiesCount = useTypedSelector(selectEntitiesCount);
77
84
  const nodesMap = useTypedSelector(selectNodesMap);
78
85
  const usageFilterOptions = useTypedSelector(selectUsageFilterOptions);
86
+ const nodesSortParams = useTypedSelector(selectNodesSortParams);
87
+ const groupsSortParams = useTypedSelector(selectGroupsSortParams);
79
88
 
80
89
  useEffect(() => {
81
90
  dispatch(getNodesList());
@@ -86,27 +95,47 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
86
95
  };
87
96
  }, [dispatch]);
88
97
 
98
+ const nodesRequestParams = useNodesRequestParams({
99
+ filter,
100
+ nodesUptimeFilter,
101
+ ...nodesSortParams,
102
+ });
103
+ const storageRequestParams = useStorageRequestParams({
104
+ filter,
105
+ ...groupsSortParams,
106
+ });
107
+
108
+ const [nodesSort, handleNodesSort] = useTableSort(nodesSortParams, (params) =>
109
+ dispatch(setNodesSortParams(params as NodesSortParams)),
110
+ );
111
+ const [groupsSort, handleGroupsSort] = useTableSort(groupsSortParams, (params) =>
112
+ dispatch(setGroupsSortParams(params as StorageSortParams)),
113
+ );
114
+
89
115
  const fetchData = useCallback(
90
116
  (isBackground: boolean) => {
91
117
  if (!isBackground) {
92
118
  dispatch(setDataWasNotLoaded());
93
119
  }
94
120
 
95
- dispatch(
96
- getStorageInfo(
97
- {
98
- tenant,
99
- nodeId,
100
- visibleEntities,
101
- type: storageType,
102
- },
103
- {
104
- concurrentId: 'getStorageInfo',
105
- },
106
- ),
107
- );
121
+ const nodesParams = nodesRequestParams || {};
122
+ const storageParams = storageRequestParams || {};
123
+
124
+ if (storageType === STORAGE_TYPES.nodes) {
125
+ dispatch(getStorageNodesInfo({tenant, visibleEntities, ...nodesParams}));
126
+ } else {
127
+ dispatch(getStorageGroupsInfo({tenant, visibleEntities, nodeId, ...storageParams}));
128
+ }
108
129
  },
109
- [dispatch, tenant, nodeId, visibleEntities, storageType],
130
+ [
131
+ dispatch,
132
+ tenant,
133
+ nodeId,
134
+ visibleEntities,
135
+ storageType,
136
+ storageRequestParams,
137
+ nodesRequestParams,
138
+ ],
110
139
  );
111
140
 
112
141
  const autorefreshEnabled = tenant ? autorefresh : true;
@@ -145,9 +174,11 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
145
174
  <StorageGroups
146
175
  visibleEntities={visibleEntities}
147
176
  data={storageGroups}
148
- tableSettings={tableSettings}
177
+ tableSettings={DEFAULT_TABLE_SETTINGS}
149
178
  nodes={nodesMap}
150
179
  onShowAll={() => handleGroupVisibilityChange(VISIBLE_ENTITIES.all)}
180
+ sort={groupsSort}
181
+ handleSort={handleGroupsSort}
151
182
  />
152
183
  )}
153
184
  {storageType === STORAGE_TYPES.nodes && (
@@ -155,9 +186,11 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
155
186
  visibleEntities={visibleEntities}
156
187
  nodesUptimeFilter={nodesUptimeFilter}
157
188
  data={storageNodes}
158
- tableSettings={tableSettings}
189
+ tableSettings={DEFAULT_TABLE_SETTINGS}
159
190
  onShowAll={handleShowAllNodes}
160
191
  additionalNodesInfo={additionalNodesInfo}
192
+ sort={nodesSort}
193
+ handleSort={handleNodesSort}
161
194
  />
162
195
  )}
163
196
  </>
@@ -166,7 +199,6 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
166
199
 
167
200
  const renderEntitiesCount = () => {
168
201
  const entityName = storageType === STORAGE_TYPES.groups ? 'Groups' : 'Nodes';
169
- const count = storageType === STORAGE_TYPES.groups ? groupsCount : nodesCount;
170
202
  const current =
171
203
  storageType === STORAGE_TYPES.groups ? storageGroups.length : storageNodes.length;
172
204
 
@@ -174,7 +206,7 @@ export const Storage = ({additionalNodesInfo, tenant, nodeId}: StorageProps) =>
174
206
  <EntitiesCount
175
207
  label={entityName}
176
208
  loading={loading && !wasLoaded}
177
- total={count.total}
209
+ total={entitiesCount.total}
178
210
  current={current}
179
211
  />
180
212
  );
@@ -3,13 +3,15 @@ import cn from 'bem-cn-lite';
3
3
  import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
4
4
  import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
5
5
 
6
+ import type {ValueOf} from '../../../types/common';
6
7
  import type {NodesMap} from '../../../types/store/nodesList';
7
8
  import type {PreparedStorageGroup, VisibleEntities} from '../../../store/reducers/storage/types';
9
+ import type {HandleSort} from '../../../utils/hooks/useTableSort';
8
10
 
9
11
  import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
10
12
  import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
11
13
  import {stringifyVdiskId} from '../../../utils';
12
- import {getUsage, isFullVDiskData} from '../../../utils/storage';
14
+ import {getUsage, isFullVDiskData, isSortableStorageProperty} from '../../../utils/storage';
13
15
 
14
16
  import shieldIcon from '../../../assets/icons/shield.svg';
15
17
  import {Stack} from '../../../components/Stack/Stack';
@@ -22,23 +24,22 @@ import {getDegradedSeverity, getUsageSeverityForStorageGroup} from '../utils';
22
24
  import i18n from './i18n';
23
25
  import './StorageGroups.scss';
24
26
 
25
- enum TableColumnsIds {
26
- PoolName = 'PoolName',
27
- Type = 'Type',
28
- ErasureSpecies = 'ErasureSpecies',
29
- GroupID = 'GroupID',
30
- Used = 'Used',
31
- Limit = 'Limit',
32
- UsedPercents = 'UsedPercents',
33
- UsedSpaceFlag = 'UsedSpaceFlag',
34
- Read = 'Read',
35
- Write = 'Write',
36
- VDisks = 'VDisks',
37
- Missing = 'Missing',
38
- }
27
+ const TableColumnsIds = {
28
+ PoolName: 'PoolName',
29
+ Kind: 'Kind',
30
+ Erasure: 'Erasure',
31
+ GroupId: 'GroupId',
32
+ Used: 'Used',
33
+ Limit: 'Limit',
34
+ Usage: 'Usage',
35
+ UsedSpaceFlag: 'UsedSpaceFlag',
36
+ Read: 'Read',
37
+ Write: 'Write',
38
+ VDisks: 'VDisks',
39
+ Degraded: 'Degraded',
40
+ } as const;
39
41
 
40
- type TableColumnsIdsKeys = keyof typeof TableColumnsIds;
41
- type TableColumnsIdsValues = typeof TableColumnsIds[TableColumnsIdsKeys];
42
+ type TableColumnId = ValueOf<typeof TableColumnsIds>;
42
43
 
43
44
  interface StorageGroupsProps {
44
45
  data: PreparedStorageGroup[];
@@ -46,59 +47,37 @@ interface StorageGroupsProps {
46
47
  tableSettings: Settings;
47
48
  visibleEntities: VisibleEntities;
48
49
  onShowAll?: VoidFunction;
50
+ sort?: SortOrder;
51
+ handleSort?: HandleSort;
49
52
  }
50
53
 
51
- const tableColumnsNames: Record<TableColumnsIdsValues, string> = {
54
+ const tableColumnsNames: Record<TableColumnId, string> = {
52
55
  PoolName: 'Pool Name',
53
- Type: 'Type',
54
- ErasureSpecies: 'Erasure',
55
- GroupID: 'Group ID',
56
+ Kind: 'Type',
57
+ Erasure: 'Erasure',
58
+ GroupId: 'Group ID',
56
59
  Used: 'Used',
57
60
  Limit: 'Limit',
58
61
  UsedSpaceFlag: 'Space',
59
- UsedPercents: 'Usage',
62
+ Usage: 'Usage',
60
63
  Read: 'Read',
61
64
  Write: 'Write',
62
65
  VDisks: 'VDisks',
63
- Missing: 'Degraded',
66
+ Degraded: 'Degraded',
64
67
  };
65
68
 
66
69
  const b = cn('global-storage-groups');
67
70
 
68
- function setSortOrder(visibleEntities: VisibleEntities): SortOrder | undefined {
69
- switch (visibleEntities) {
70
- case VISIBLE_ENTITIES.all: {
71
- return {
72
- columnId: TableColumnsIds.PoolName,
73
- order: DataTable.ASCENDING,
74
- };
75
- }
76
- case VISIBLE_ENTITIES.missing: {
77
- return {
78
- columnId: TableColumnsIds.Missing,
79
- order: DataTable.DESCENDING,
80
- };
81
- }
82
- case VISIBLE_ENTITIES.space: {
83
- return {
84
- columnId: TableColumnsIds.UsedSpaceFlag,
85
- order: DataTable.DESCENDING,
86
- };
87
- }
88
- default: {
89
- return undefined;
90
- }
91
- }
92
- }
93
-
94
71
  export function StorageGroups({
95
72
  data,
96
73
  tableSettings,
97
74
  visibleEntities,
98
75
  nodes,
99
76
  onShowAll,
77
+ sort,
78
+ handleSort,
100
79
  }: StorageGroupsProps) {
101
- const allColumns: Column<PreparedStorageGroup>[] = [
80
+ const rawColumns: Column<PreparedStorageGroup>[] = [
102
81
  {
103
82
  name: TableColumnsIds.PoolName,
104
83
  header: tableColumnsNames[TableColumnsIds.PoolName],
@@ -124,12 +103,12 @@ export function StorageGroups({
124
103
  align: DataTable.LEFT,
125
104
  },
126
105
  {
127
- name: TableColumnsIds.Type,
128
- header: tableColumnsNames[TableColumnsIds.Type],
106
+ name: TableColumnsIds.Kind,
107
+ header: tableColumnsNames[TableColumnsIds.Kind],
129
108
  // prettier-ignore
130
109
  render: ({row}) => (
131
110
  <>
132
- <Label>{row.Type || '—'}</Label>
111
+ <Label>{row.Kind || '—'}</Label>
133
112
  {' '}
134
113
  {row.Encryption && (
135
114
  <Popover
@@ -146,18 +125,18 @@ export function StorageGroups({
146
125
  ),
147
126
  },
148
127
  {
149
- name: TableColumnsIds.ErasureSpecies,
150
- header: tableColumnsNames[TableColumnsIds.ErasureSpecies],
128
+ name: TableColumnsIds.Erasure,
129
+ header: tableColumnsNames[TableColumnsIds.Erasure],
151
130
  render: ({row}) => (row.ErasureSpecies ? row.ErasureSpecies : '-'),
152
131
  align: DataTable.LEFT,
153
132
  },
154
133
  {
155
- name: TableColumnsIds.Missing,
156
- header: tableColumnsNames[TableColumnsIds.Missing],
134
+ name: TableColumnsIds.Degraded,
135
+ header: tableColumnsNames[TableColumnsIds.Degraded],
157
136
  width: 100,
158
137
  render: ({row}) =>
159
- row.Missing ? (
160
- <Label theme={getDegradedSeverity(row)}>Degraded: {row.Missing}</Label>
138
+ row.Degraded ? (
139
+ <Label theme={getDegradedSeverity(row)}>Degraded: {row.Degraded}</Label>
161
140
  ) : (
162
141
  '-'
163
142
  ),
@@ -165,19 +144,18 @@ export function StorageGroups({
165
144
  defaultOrder: DataTable.DESCENDING,
166
145
  },
167
146
  {
168
- name: TableColumnsIds.UsedPercents,
169
- header: tableColumnsNames[TableColumnsIds.UsedPercents],
147
+ name: TableColumnsIds.Usage,
148
+ header: tableColumnsNames[TableColumnsIds.Usage],
170
149
  width: 100,
171
150
  render: ({row}) => {
172
- const usage = getUsage(row, 5);
173
151
  // without a limit the usage can be evaluated as 0,
174
152
  // but the absence of a value is more clear
175
153
  return row.Limit ? (
176
154
  <Label
177
- theme={getUsageSeverityForStorageGroup(usage)}
178
- className={b('usage-label', {overload: usage >= 90})}
155
+ theme={getUsageSeverityForStorageGroup(row.Usage)}
156
+ className={b('usage-label', {overload: row.Usage >= 90})}
179
157
  >
180
- {usage}%
158
+ {row.Usage}%
181
159
  </Label>
182
160
  ) : (
183
161
  '-'
@@ -188,12 +166,13 @@ export function StorageGroups({
188
166
  align: DataTable.LEFT,
189
167
  },
190
168
  {
191
- name: TableColumnsIds.GroupID,
192
- header: tableColumnsNames[TableColumnsIds.GroupID],
169
+ name: TableColumnsIds.GroupId,
170
+ header: tableColumnsNames[TableColumnsIds.GroupId],
193
171
  width: 130,
194
172
  render: ({row}) => {
195
173
  return <span className={b('group-id')}>{row.GroupID}</span>;
196
174
  },
175
+ sortAccessor: (row) => Number(row.GroupID),
197
176
  align: DataTable.RIGHT,
198
177
  },
199
178
  {
@@ -297,18 +276,21 @@ export function StorageGroups({
297
276
  },
298
277
  ];
299
278
 
300
- let columns = allColumns;
279
+ let columns = rawColumns.map((column) => ({
280
+ ...column,
281
+ sortable: isSortableStorageProperty(column.name),
282
+ }));
301
283
 
302
284
  if (visibleEntities === VISIBLE_ENTITIES.all) {
303
- columns = allColumns.filter((col) => {
285
+ columns = columns.filter((col) => {
304
286
  return (
305
- col.name !== TableColumnsIds.Missing && col.name !== TableColumnsIds.UsedSpaceFlag
287
+ col.name !== TableColumnsIds.Degraded && col.name !== TableColumnsIds.UsedSpaceFlag
306
288
  );
307
289
  });
308
290
  }
309
291
 
310
292
  if (visibleEntities === VISIBLE_ENTITIES.space) {
311
- columns = allColumns.filter((col) => col.name !== TableColumnsIds.Missing);
293
+ columns = columns.filter((col) => col.name !== TableColumnsIds.Degraded);
312
294
 
313
295
  if (!data.length) {
314
296
  return (
@@ -322,7 +304,7 @@ export function StorageGroups({
322
304
  }
323
305
 
324
306
  if (visibleEntities === VISIBLE_ENTITIES.missing) {
325
- columns = allColumns.filter((col) => col.name !== TableColumnsIds.UsedSpaceFlag);
307
+ columns = columns.filter((col) => col.name !== TableColumnsIds.UsedSpaceFlag);
326
308
 
327
309
  if (!data.length) {
328
310
  return (
@@ -342,8 +324,9 @@ export function StorageGroups({
342
324
  data={data}
343
325
  columns={columns}
344
326
  settings={tableSettings}
345
- initialSortOrder={setSortOrder(visibleEntities)}
346
327
  emptyDataMessage={i18n('empty.default')}
328
+ sortOrder={sort}
329
+ onSort={handleSort}
347
330
  />
348
331
  ) : null;
349
332
  }
@@ -2,11 +2,14 @@ import cn from 'bem-cn-lite';
2
2
 
3
3
  import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
4
4
 
5
+ import type {ValueOf} from '../../../types/common';
5
6
  import type {PreparedStorageNode, VisibleEntities} from '../../../store/reducers/storage/types';
7
+ import type {HandleSort} from '../../../utils/hooks/useTableSort';
6
8
 
7
9
  import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
8
10
  import {
9
11
  AdditionalNodesInfo,
12
+ isSortableNodesProperty,
10
13
  isUnavailableNode,
11
14
  NodesUptimeFilterValues,
12
15
  } from '../../../utils/nodes';
@@ -19,18 +22,17 @@ import {PDisk} from '../PDisk';
19
22
  import i18n from './i18n';
20
23
  import './StorageNodes.scss';
21
24
 
22
- enum TableColumnsIds {
23
- NodeId = 'NodeId',
24
- FQDN = 'FQDN',
25
- DataCenter = 'DataCenter',
26
- Rack = 'Rack',
27
- Uptime = 'Uptime',
28
- PDisks = 'PDisks',
29
- Missing = 'Missing',
30
- }
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;
31
34
 
32
- type TableColumnsIdsKeys = keyof typeof TableColumnsIds;
33
- type TableColumnsIdsValues = typeof TableColumnsIds[TableColumnsIdsKeys];
35
+ type TableColumnId = ValueOf<typeof TableColumnsIds>;
34
36
 
35
37
  interface StorageNodesProps {
36
38
  data: PreparedStorageNode[];
@@ -39,12 +41,14 @@ interface StorageNodesProps {
39
41
  nodesUptimeFilter: keyof typeof NodesUptimeFilterValues;
40
42
  onShowAll?: VoidFunction;
41
43
  additionalNodesInfo?: AdditionalNodesInfo;
44
+ sort?: SortOrder;
45
+ handleSort?: HandleSort;
42
46
  }
43
47
 
44
- const tableColumnsNames: Record<TableColumnsIdsValues, string> = {
48
+ const tableColumnsNames: Record<TableColumnId, string> = {
45
49
  NodeId: 'Node ID',
46
- FQDN: 'FQDN',
47
- DataCenter: 'DC',
50
+ Host: 'Host',
51
+ DC: 'DC',
48
52
  Rack: 'Rack',
49
53
  Uptime: 'Uptime',
50
54
  PDisks: 'PDisks',
@@ -53,26 +57,6 @@ const tableColumnsNames: Record<TableColumnsIdsValues, string> = {
53
57
 
54
58
  const b = cn('global-storage-nodes');
55
59
 
56
- function setSortOrder(visibleEntities: VisibleEntities): SortOrder | undefined {
57
- switch (visibleEntities) {
58
- case VISIBLE_ENTITIES.all: {
59
- return {
60
- columnId: TableColumnsIds.NodeId,
61
- order: DataTable.ASCENDING,
62
- };
63
- }
64
- case VISIBLE_ENTITIES.missing: {
65
- return {
66
- columnId: TableColumnsIds.Missing,
67
- order: DataTable.DESCENDING,
68
- };
69
- }
70
- default: {
71
- return undefined;
72
- }
73
- }
74
- }
75
-
76
60
  export function StorageNodes({
77
61
  data,
78
62
  tableSettings,
@@ -80,10 +64,12 @@ export function StorageNodes({
80
64
  onShowAll,
81
65
  nodesUptimeFilter,
82
66
  additionalNodesInfo,
67
+ sort,
68
+ handleSort,
83
69
  }: StorageNodesProps) {
84
70
  const getNodeRef = additionalNodesInfo?.getNodeRef;
85
71
 
86
- const allColumns: Column<PreparedStorageNode>[] = [
72
+ const rawColumns: Column<PreparedStorageNode>[] = [
87
73
  {
88
74
  name: TableColumnsIds.NodeId,
89
75
  header: tableColumnsNames[TableColumnsIds.NodeId],
@@ -91,8 +77,8 @@ export function StorageNodes({
91
77
  align: DataTable.RIGHT,
92
78
  },
93
79
  {
94
- name: TableColumnsIds.FQDN,
95
- header: tableColumnsNames[TableColumnsIds.FQDN],
80
+ name: TableColumnsIds.Host,
81
+ header: tableColumnsNames[TableColumnsIds.Host],
96
82
  width: 350,
97
83
  render: ({row}) => {
98
84
  return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
@@ -100,8 +86,8 @@ export function StorageNodes({
100
86
  align: DataTable.LEFT,
101
87
  },
102
88
  {
103
- name: TableColumnsIds.DataCenter,
104
- header: tableColumnsNames[TableColumnsIds.DataCenter],
89
+ name: TableColumnsIds.DC,
90
+ header: tableColumnsNames[TableColumnsIds.DC],
105
91
  render: ({row}) => row.DataCenter || '—',
106
92
  align: DataTable.LEFT,
107
93
  },
@@ -144,10 +130,13 @@ export function StorageNodes({
144
130
  },
145
131
  ];
146
132
 
147
- let columns = allColumns;
133
+ let columns = rawColumns.map((column) => ({
134
+ ...column,
135
+ sortable: isSortableNodesProperty(column.name),
136
+ }));
148
137
 
149
- if (visibleEntities === VISIBLE_ENTITIES.space) {
150
- columns = allColumns.filter((col) => col.name !== TableColumnsIds.Missing);
138
+ if (visibleEntities !== VISIBLE_ENTITIES.missing) {
139
+ columns = columns.filter((col) => col.name !== TableColumnsIds.Missing);
151
140
  }
152
141
 
153
142
  if (!data.length) {
@@ -187,9 +176,10 @@ export function StorageNodes({
187
176
  ...tableSettings,
188
177
  dynamicRenderType: 'variable',
189
178
  }}
190
- initialSortOrder={setSortOrder(visibleEntities)}
191
179
  emptyDataMessage={i18n('empty.default')}
192
180
  rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})}
181
+ sortOrder={sort}
182
+ onSort={handleSort}
193
183
  />
194
184
  ) : null;
195
185
  }
@@ -1,4 +1,4 @@
1
- import type {IStoragePoolGroup} from '../../../types/store/storage';
1
+ import type {PreparedStorageGroup} from '../../../store/reducers/storage/types';
2
2
  import {EFlag} from '../../../types/api/enums';
3
3
 
4
4
  export * from './constants';
@@ -35,12 +35,12 @@ const degradationEvaluators = {
35
35
  const canEvaluateErasureSpecies = (value?: string): value is keyof typeof degradationEvaluators =>
36
36
  value !== undefined && value in degradationEvaluators;
37
37
 
38
- export const getDegradedSeverity = (group: IStoragePoolGroup) => {
38
+ export const getDegradedSeverity = (group: PreparedStorageGroup) => {
39
39
  const evaluate = canEvaluateErasureSpecies(group.ErasureSpecies)
40
40
  ? degradationEvaluators[group.ErasureSpecies]
41
41
  : defaultDegradationEvaluator;
42
42
 
43
- return evaluate(group.Missing);
43
+ return evaluate(group.Degraded);
44
44
  };
45
45
 
46
46
  export const getUsageSeverityForStorageGroup = generateEvaluator(80, 85, [
@@ -13,6 +13,7 @@ import {DEVELOPER_UI_TITLE} from '../../utils/constants';
13
13
  import '../../services/api';
14
14
  import {parseQuery} from '../../routes';
15
15
 
16
+ import type {EType} from '../../types/api/tablet';
16
17
  import EntityStatus from '../../components/EntityStatus/EntityStatus';
17
18
  import {ResponseError} from '../../components/Errors/ResponseError';
18
19
  import {Tag} from '../../components/Tag';
@@ -48,10 +49,14 @@ export const Tablet = () => {
48
49
  error,
49
50
  } = useTypedSelector((state) => state.tablet);
50
51
 
51
- const {nodeId: queryNodeId, tenantName: queryTenantName} = parseQuery(location);
52
-
52
+ const {
53
+ nodeId: queryNodeId,
54
+ tenantName: queryTenantName,
55
+ type: queryTabletType,
56
+ } = parseQuery(location);
53
57
  const nodeId = tablet.NodeId?.toString() || queryNodeId?.toString();
54
58
  const tenantName = tenantPath || queryTenantName?.toString();
59
+ const type = tablet.Type || (queryTabletType?.toString() as EType | undefined);
55
60
 
56
61
  // NOTE: should be reviewed when migrating to React 18
57
62
  useEffect(() => {
@@ -79,9 +84,10 @@ export const Tablet = () => {
79
84
  nodeIds: nodeId ? [nodeId] : [],
80
85
  tenantName,
81
86
  tabletId: id,
87
+ tabletType: type,
82
88
  }),
83
89
  );
84
- }, [dispatch, tenantName, id, nodeId]);
90
+ }, [dispatch, tenantName, id, nodeId, type]);
85
91
 
86
92
  const renderExternalLinks = (link: {name: string; path: string}, index: number) => {
87
93
  return (
@@ -5,4 +5,12 @@
5
5
  margin-left: 10px;
6
6
 
7
7
  color: var(--yc-color-text-complementary);
8
+
9
+ &__item-with-popover {
10
+ white-space: nowrap;
11
+ }
12
+
13
+ &__popover {
14
+ max-width: 300px;
15
+ }
8
16
  }