ydb-embedded-ui 4.14.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/containers/App/App.js +1 -1
  3. package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
  4. package/dist/containers/Authentication/Authentication.tsx +1 -1
  5. package/dist/containers/Storage/Storage.tsx +64 -32
  6. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +56 -73
  7. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +33 -43
  8. package/dist/containers/Storage/utils/index.ts +3 -3
  9. package/dist/containers/Tenant/i18n/en.json +3 -0
  10. package/dist/containers/Tenant/i18n/ru.json +3 -0
  11. package/dist/containers/Tenant/utils/queryTemplates.ts +89 -0
  12. package/dist/containers/Tenant/utils/schema.ts +1 -1
  13. package/dist/containers/Tenant/utils/schemaActions.ts +27 -44
  14. package/dist/containers/UserSettings/i18n/en.json +1 -1
  15. package/dist/containers/UserSettings/i18n/ru.json +1 -1
  16. package/dist/{reportWebVitals.js → reportWebVitals.ts} +3 -1
  17. package/dist/services/api.ts +6 -4
  18. package/dist/store/reducers/{authentication.js → authentication/authentication.ts} +14 -7
  19. package/dist/store/reducers/authentication/types.ts +15 -0
  20. package/dist/store/reducers/describe.ts +1 -16
  21. package/dist/store/reducers/index.ts +1 -1
  22. package/dist/store/reducers/storage/selectors.ts +50 -150
  23. package/dist/store/reducers/storage/storage.ts +73 -25
  24. package/dist/store/reducers/storage/types.ts +49 -17
  25. package/dist/store/reducers/storage/utils.ts +207 -0
  26. package/dist/store/utils.ts +1 -1
  27. package/dist/types/api/error.ts +4 -0
  28. package/dist/types/api/storage.ts +32 -4
  29. package/dist/types/window.d.ts +1 -0
  30. package/dist/utils/hooks/index.ts +1 -0
  31. package/dist/utils/hooks/useStorageRequestParams.ts +28 -0
  32. package/dist/utils/hooks/useTableSort.ts +1 -1
  33. package/dist/utils/storage.ts +31 -3
  34. package/package.json +1 -5
  35. package/dist/HOCS/WithSearch/WithSearch.js +0 -26
  36. package/dist/HOCS/index.js +0 -1
  37. package/dist/components/Hotkey/Hotkey.js +0 -102
  38. package/dist/components/Pagination/Pagination.js +0 -63
  39. package/dist/components/Pagination/Pagination.scss +0 -28
  40. package/dist/types/store/storage.ts +0 -12
  41. /package/dist/{index.js → index.tsx} +0 -0
  42. /package/dist/utils/{monaco.js → monaco.ts} +0 -0
@@ -14,8 +14,11 @@
14
14
  "actions.openPreview": "Открыть превью",
15
15
  "actions.createTable": "Создать таблицу...",
16
16
  "actions.createExternalTable": "Создать внешнюю таблицу...",
17
+ "actions.createTopic": "Создать топик...",
17
18
  "actions.dropTable": "Удалить таблицу...",
19
+ "actions.dropTopic": "Удалить топик...",
18
20
  "actions.alterTable": "Изменить таблицу...",
21
+ "actions.alterTopic": "Изменить топик...",
19
22
  "actions.selectQuery": "Select запрос...",
20
23
  "actions.upsertQuery": "Upsert запрос..."
21
24
  }
@@ -0,0 +1,89 @@
1
+ export const createTableTemplate = (path: string) => {
2
+ return `CREATE TABLE \`${path}/my_table\`
3
+ (
4
+ \`id\` Uint64,
5
+ \`name\` String,
6
+ PRIMARY KEY (\`id\`)
7
+ );`;
8
+ };
9
+ export const alterTableTemplate = (path: string) => {
10
+ return `ALTER TABLE \`${path}\`
11
+ ADD COLUMN is_deleted Bool;`;
12
+ };
13
+ export const selectQueryTemplate = (path: string) => {
14
+ return `SELECT *
15
+ FROM \`${path}\`
16
+ LIMIT 10;`;
17
+ };
18
+ export const upsertQueryTemplate = (path: string) => {
19
+ return `UPSERT INTO \`${path}\`
20
+ ( \`id\`, \`name\` )
21
+ VALUES ( );`;
22
+ };
23
+
24
+ export const dropExternalTableTemplate = (path: string) => {
25
+ return `DROP EXTERNAL TABLE \`${path}\`;`;
26
+ };
27
+
28
+ export const createExternalTableTemplate = (path: string) => {
29
+ // Remove data source name from path
30
+ // to create table in the same folder with data source
31
+ const targetPath = path.split('/').slice(0, -1).join('/');
32
+
33
+ return `CREATE EXTERNAL TABLE \`${targetPath}/my_external_table\` (
34
+ column1 Int,
35
+ column2 Int
36
+ ) WITH (
37
+ DATA_SOURCE="${path}",
38
+ LOCATION="",
39
+ FORMAT="json_as_string",
40
+ \`file_pattern\`=""
41
+ );`;
42
+ };
43
+
44
+ export const createTopicTemplate = (path: string) => {
45
+ return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/create_topic
46
+ CREATE TOPIC \`${path}/my_topic\` (
47
+ CONSUMER consumer1,
48
+ CONSUMER consumer2 WITH (read_from = Datetime('2022-12-01T12:13:22Z')) -- Sets up the message write time starting from which the consumer will receive data.
49
+ -- Value type: Datetime OR Timestamp OR integer (unix-timestamp in the numeric format).
50
+ -- Default value: now
51
+ ) WITH (
52
+ min_active_partitions = 5, -- Minimum number of topic partitions.
53
+ partition_count_limit = 10, -- Maximum number of active partitions in the topic. 0 is interpreted as unlimited.
54
+ retention_period = Interval('PT12H'), -- Data retention period in the topic. Value type: Interval, default value: 18h.
55
+ retention_storage_mb = 1, -- Limit on the maximum disk space occupied by the topic data.
56
+ -- When this value is exceeded, the older data is cleared, like under a retention policy.
57
+ -- 0 is interpreted as unlimited.
58
+ partition_write_speed_bytes_per_second = 2097152, -- Maximum allowed write speed per partition.
59
+ partition_write_burst_bytes = 2097152 -- Write quota allocated for write bursts.
60
+ -- When set to zero, the actual write_burst value is equalled to
61
+ -- the quota value (this allows write bursts of up to one second).
62
+ );`;
63
+ };
64
+
65
+ export const alterTopicTemplate = (path: string) => {
66
+ return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/alter_topic
67
+ ALTER TOPIC \`${path}\`
68
+ ADD CONSUMER new_consumer WITH (read_from = 0), -- Sets up the message write time starting from which the consumer will receive data.
69
+ -- Value type: Datetime OR Timestamp OR integer (unix-timestamp in the numeric format).
70
+ -- Default value: now
71
+ ALTER CONSUMER consumer1 SET (read_from = Datetime('2023-12-01T12:13:22Z')),
72
+ DROP CONSUMER consumer2,
73
+ SET (
74
+ min_active_partitions = 10, -- Minimum number of topic partitions.
75
+ partition_count_limit = 15, -- Maximum number of active partitions in the topic. 0 is interpreted as unlimited.
76
+ retention_period = Interval('PT36H'), -- Data retention period in the topic. Value type: Interval, default value: 18h.
77
+ retention_storage_mb = 10, -- Limit on the maximum disk space occupied by the topic data.
78
+ -- When this value is exceeded, the older data is cleared, like under a retention policy.
79
+ -- 0 is interpreted as unlimited.
80
+ partition_write_speed_bytes_per_second = 3145728, -- Maximum allowed write speed per partition.
81
+ partition_write_burst_bytes = 1048576 -- Write quota allocated for write bursts.
82
+ -- When set to zero, the actual write_burst value is equalled to
83
+ -- the quota value (this allows write bursts of up to one second).
84
+ );`;
85
+ };
86
+
87
+ export const dropTopicTemplate = (path: string) => {
88
+ return `DROP TOPIC \`${path}\`;`;
89
+ };
@@ -30,7 +30,7 @@ const pathTypeToNodeType: Record<EPathType, NavigationTreeNodeType | undefined>
30
30
 
31
31
  [EPathType.EPathTypeColumnTable]: 'column_table',
32
32
 
33
- [EPathType.EPathTypeCdcStream]: 'topic',
33
+ [EPathType.EPathTypeCdcStream]: 'stream',
34
34
  [EPathType.EPathTypePersQueueGroup]: 'topic',
35
35
 
36
36
  [EPathType.EPathTypeExternalDataSource]: 'external_data_source',
@@ -12,48 +12,17 @@ import createToast from '../../../utils/createToast';
12
12
 
13
13
  import i18n from '../i18n';
14
14
 
15
- const createTableTemplate = (path: string) => {
16
- return `CREATE TABLE \`${path}/my_table\`
17
- (
18
- \`id\` Uint64,
19
- \`name\` String,
20
- PRIMARY KEY (\`id\`)
21
- );`;
22
- };
23
- const alterTableTemplate = (path: string) => {
24
- return `ALTER TABLE \`${path}\`
25
- ADD COLUMN is_deleted Bool;`;
26
- };
27
- const selectQueryTemplate = (path: string) => {
28
- return `SELECT *
29
- FROM \`${path}\`
30
- LIMIT 10;`;
31
- };
32
- const upsertQueryTemplate = (path: string) => {
33
- return `UPSERT INTO \`${path}\`
34
- ( \`id\`, \`name\` )
35
- VALUES ( );`;
36
- };
37
-
38
- const dropExternalTableTemplate = (path: string) => {
39
- return `DROP EXTERNAL TABLE \`${path}\`;`;
40
- };
41
-
42
- const createExternalTableTemplate = (path: string) => {
43
- // Remove data source name from path
44
- // to create table in the same folder with data source
45
- const targetPath = path.split('/').slice(0, -1).join('/');
46
-
47
- return `CREATE EXTERNAL TABLE \`${targetPath}/my_external_table\` (
48
- column1 Int,
49
- column2 Int
50
- ) WITH (
51
- DATA_SOURCE="${path}",
52
- LOCATION="",
53
- FORMAT="json_as_string",
54
- \`file_pattern\`=""
55
- );`;
56
- };
15
+ import {
16
+ alterTableTemplate,
17
+ alterTopicTemplate,
18
+ createExternalTableTemplate,
19
+ createTableTemplate,
20
+ createTopicTemplate,
21
+ dropExternalTableTemplate,
22
+ dropTopicTemplate,
23
+ selectQueryTemplate,
24
+ upsertQueryTemplate,
25
+ } from './queryTemplates';
57
26
 
58
27
  interface ActionsAdditionalEffects {
59
28
  setQueryMode: SetQueryModeIfAvailable;
@@ -92,6 +61,9 @@ const bindActions = (
92
61
  'query',
93
62
  i18n('actions.externalTableSelectUnavailable'),
94
63
  ),
64
+ createTopic: inputQuery(createTopicTemplate, 'script'),
65
+ alterTopic: inputQuery(alterTopicTemplate, 'script'),
66
+ dropTopic: inputQuery(dropTopicTemplate, 'script'),
95
67
  copyPath: () => {
96
68
  try {
97
69
  copy(path);
@@ -121,7 +93,10 @@ export const getActions =
121
93
 
122
94
  const DIR_SET: ActionsSet = [
123
95
  [copyItem],
124
- [{text: i18n('actions.createTable'), action: actions.createTable}],
96
+ [
97
+ {text: i18n('actions.createTable'), action: actions.createTable},
98
+ {text: i18n('actions.createTopic'), action: actions.createTopic},
99
+ ],
125
100
  ];
126
101
  const TABLE_SET: ActionsSet = [
127
102
  [copyItem],
@@ -132,6 +107,14 @@ export const getActions =
132
107
  ],
133
108
  ];
134
109
 
110
+ const TOPIC_SET: ActionsSet = [
111
+ [copyItem],
112
+ [
113
+ {text: i18n('actions.alterTopic'), action: actions.alterTopic},
114
+ {text: i18n('actions.dropTopic'), action: actions.dropTopic},
115
+ ],
116
+ ];
117
+
135
118
  const EXTERNAL_TABLE_SET = [
136
119
  [copyItem],
137
120
  [
@@ -160,7 +143,7 @@ export const getActions =
160
143
  column_table: TABLE_SET,
161
144
 
162
145
  index_table: JUST_COPY,
163
- topic: JUST_COPY,
146
+ topic: TOPIC_SET,
164
147
  stream: JUST_COPY,
165
148
 
166
149
  index: JUST_COPY,
@@ -16,7 +16,7 @@
16
16
  "settings.useNodesEndpoint.popover": "Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on versions before 23-1",
17
17
 
18
18
  "settings.useBackendParamsForTables.title": "Offload tables filters and sorting to backend",
19
- "settings.useBackendParamsForTables.popover": "Filter and sort Nodes table with request params. May increase performance, but could causes additional fetches and longer loading time on older versions",
19
+ "settings.useBackendParamsForTables.popover": "Filter and sort Nodes and Storage tables with request params. May increase performance, but could causes additional fetches and longer loading time on older versions",
20
20
 
21
21
  "settings.enableAdditionalQueryModes.title": "Enable additional query modes",
22
22
  "settings.enableAdditionalQueryModes.popover": "Adds 'Data' and 'YQL - QueryService' modes. May not work on some versions"
@@ -16,7 +16,7 @@
16
16
  "settings.useNodesEndpoint.popover": "Использовать эндпоинт /viewer/json/nodes для вкладки Nodes в диагностике. Может возвращать некорректные данные на версиях до 23-1",
17
17
 
18
18
  "settings.useBackendParamsForTables.title": "Перенести фильтры и сортировку таблиц на бэкенд",
19
- "settings.useBackendParamsForTables.popover": "Добавляет фильтрацию и сортировку таблицы Nodes с использованием параметров запроса. Может улушить производительность, но на старых версиях может привести к дополнительным запросам и большему времени ожидания загрузки",
19
+ "settings.useBackendParamsForTables.popover": "Добавляет фильтрацию и сортировку таблиц Nodes и Storage с использованием параметров запроса. Может улушить производительность, но на старых версиях может привести к дополнительным запросам и большему времени ожидания загрузки",
20
20
 
21
21
  "settings.enableAdditionalQueryModes.title": "Включить дополнительные режимы выполнения запросов",
22
22
  "settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data' и 'YQL - QueryService'. Может работать некорректно на некоторых версиях"
@@ -1,4 +1,6 @@
1
- const reportWebVitals = (onPerfEntry) => {
1
+ import type {ReportHandler} from 'web-vitals';
2
+
3
+ const reportWebVitals = (onPerfEntry?: ReportHandler) => {
2
4
  if (onPerfEntry && onPerfEntry instanceof Function) {
3
5
  import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
4
6
  getCLS(onPerfEntry);
@@ -114,19 +114,21 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
114
114
  );
115
115
  }
116
116
  getStorageInfo(
117
- {tenant, visibleEntities, nodeId}: StorageApiRequestParams,
117
+ {tenant, visibleEntities, nodeId, sortOrder, sortValue, ...params}: StorageApiRequestParams,
118
118
  {concurrentId}: AxiosOptions = {},
119
119
  ) {
120
+ const sort = prepareSortValue(sortValue, sortOrder);
121
+
120
122
  return this.get<TStorageInfo>(
121
123
  this.getPath(`/viewer/json/storage?enums=true`),
122
124
  {
123
125
  tenant,
124
126
  node_id: nodeId,
125
127
  with: visibleEntities,
128
+ sort,
129
+ ...params,
126
130
  },
127
- {
128
- concurrentId,
129
- },
131
+ {concurrentId},
130
132
  );
131
133
  }
132
134
  getPdiskInfo(nodeId: string | number, pdiskId: string | number) {
@@ -1,5 +1,9 @@
1
- import {createRequestActionTypes, createApiRequest} from '../utils';
2
- import '../../services/api';
1
+ import type {Reducer} from 'redux';
2
+
3
+ import '../../../services/api';
4
+ import {createRequestActionTypes, createApiRequest} from '../../utils';
5
+
6
+ import type {AuthenticationAction, AuthenticationState} from './types';
3
7
 
4
8
  export const SET_UNAUTHENTICATED = createRequestActionTypes(
5
9
  'authentication',
@@ -11,16 +15,19 @@ export const FETCH_USER = createRequestActionTypes('authentication', 'FETCH_USER
11
15
  const initialState = {
12
16
  isAuthenticated: true,
13
17
  user: '',
14
- error: '',
18
+ error: undefined,
15
19
  };
16
20
 
17
- const authentication = function (state = initialState, action) {
21
+ const authentication: Reducer<AuthenticationState, AuthenticationAction> = (
22
+ state = initialState,
23
+ action,
24
+ ) => {
18
25
  switch (action.type) {
19
26
  case SET_UNAUTHENTICATED.SUCCESS: {
20
- return {...state, isAuthenticated: false, user: '', error: ''};
27
+ return {...state, isAuthenticated: false, user: '', error: undefined};
21
28
  }
22
29
  case SET_AUTHENTICATED.SUCCESS: {
23
- return {...state, isAuthenticated: true, error: ''};
30
+ return {...state, isAuthenticated: true, error: undefined};
24
31
  }
25
32
  case SET_AUTHENTICATED.FAILURE: {
26
33
  return {...state, error: action.error};
@@ -34,7 +41,7 @@ const authentication = function (state = initialState, action) {
34
41
  }
35
42
  };
36
43
 
37
- export const authenticate = (user, password) => {
44
+ export const authenticate = (user: string, password: string) => {
38
45
  return createApiRequest({
39
46
  request: window.api.authenticate(user, password),
40
47
  actions: SET_AUTHENTICATED,
@@ -0,0 +1,15 @@
1
+ import type {AuthErrorResponse} from '../../../types/api/error';
2
+ import type {ApiRequestAction} from '../../utils';
3
+
4
+ import {FETCH_USER, SET_AUTHENTICATED, SET_UNAUTHENTICATED} from './authentication';
5
+
6
+ export interface AuthenticationState {
7
+ isAuthenticated: boolean;
8
+ user: string | undefined;
9
+ error: AuthErrorResponse | undefined;
10
+ }
11
+
12
+ export type AuthenticationAction =
13
+ | ApiRequestAction<typeof SET_UNAUTHENTICATED, unknown, unknown>
14
+ | ApiRequestAction<typeof SET_AUTHENTICATED, unknown, AuthErrorResponse>
15
+ | ApiRequestAction<typeof FETCH_USER, string | undefined, unknown>;
@@ -1,9 +1,7 @@
1
- import {createSelector, Selector} from 'reselect';
2
1
  import {Reducer} from 'redux';
3
2
 
4
3
  import '../../services/api';
5
- import {
6
- IDescribeRootStateSlice,
4
+ import type {
7
5
  IDescribeState,
8
6
  IDescribeAction,
9
7
  IDescribeHandledResponse,
@@ -93,19 +91,6 @@ export const setDataWasNotLoaded = () => {
93
91
  } as const;
94
92
  };
95
93
 
96
- // Consumers selectors
97
- const selectConsumersNames = (state: IDescribeRootStateSlice, path?: string) =>
98
- path
99
- ? state.describe.data[path]?.PathDescription?.PersQueueGroup?.PQTabletConfig?.ReadRules
100
- : undefined;
101
-
102
- interface IConsumer {
103
- name: string;
104
- }
105
-
106
- export const selectConsumers: Selector<IDescribeRootStateSlice, IConsumer[], [string | undefined]> =
107
- createSelector(selectConsumersNames, (names = []) => names.map((name) => ({name})));
108
-
109
94
  export function getDescribe({path}: {path: string}) {
110
95
  const request = window.api.getDescribe({path});
111
96
  return createApiRequest({
@@ -30,7 +30,7 @@ import healthcheckInfo from './healthcheckInfo';
30
30
  import shardsWorkload from './shardsWorkload';
31
31
  import hotKeys from './hotKeys';
32
32
  import olapStats from './olapStats';
33
- import authentication from './authentication';
33
+ import authentication from './authentication/authentication';
34
34
  import header from './header/header';
35
35
  import saveQuery from './saveQuery';
36
36
  import fullscreen from './fullscreen';
@@ -1,128 +1,20 @@
1
1
  import {Selector, createSelector} from 'reselect';
2
- import {getUsage} from '../../../utils/storage';
3
2
 
4
- import type {TNodeInfo} from '../../../types/api/nodes';
5
- import {TPDiskState} from '../../../types/api/pdisk';
6
- import {EVDiskState, TVDiskStateInfo} from '../../../types/api/vdisk';
7
- import {EFlag} from '../../../types/api/enums';
8
- import {getPDiskType} from '../../../utils/pdisk';
9
- import {calcUptime} from '../../../utils';
3
+ import type {OrderType} from '@gravity-ui/react-data-table';
4
+ import {ASCENDING, DESCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
10
5
 
6
+ import type {TVDiskStateInfo} from '../../../types/api/vdisk';
7
+ import {NODES_SORT_VALUES, type NodesSortValue} from '../../../utils/nodes';
8
+ import {STORAGE_SORT_VALUES, type StorageSortValue, getUsage} from '../../../utils/storage';
9
+
10
+ import {filterNodesByUptime} from '../nodes/selectors';
11
11
  import type {
12
12
  PreparedStorageGroup,
13
13
  PreparedStorageNode,
14
- RawStorageGroup,
15
14
  StorageStateSlice,
16
15
  UsageFilter,
17
16
  } from './types';
18
- import {filterNodesByUptime} from '../nodes/selectors';
19
-
20
- // ==== Prepare data ====
21
- const FLAGS_POINTS = {
22
- [EFlag.Green]: 1,
23
- [EFlag.Yellow]: 100,
24
- [EFlag.Orange]: 10_000,
25
- [EFlag.Red]: 1_000_000,
26
- };
27
-
28
- const prepareStorageGroupData = (
29
- group: RawStorageGroup,
30
- poolName?: string,
31
- ): PreparedStorageGroup => {
32
- let missing = 0;
33
- let usedSpaceFlag = 0;
34
- let usedSpaceBytes = 0;
35
- let limitSizeBytes = 0;
36
- let readSpeedBytesPerSec = 0;
37
- let writeSpeedBytesPerSec = 0;
38
- let mediaType = '';
39
-
40
- if (group.VDisks) {
41
- for (const vDisk of group.VDisks) {
42
- const {
43
- Replicated,
44
- VDiskState,
45
- AvailableSize,
46
- AllocatedSize,
47
- PDisk,
48
- DiskSpace,
49
- ReadThroughput,
50
- WriteThroughput,
51
- } = vDisk;
52
-
53
- if (
54
- !Replicated ||
55
- PDisk?.State !== TPDiskState.Normal ||
56
- VDiskState !== EVDiskState.OK
57
- ) {
58
- missing += 1;
59
- }
60
-
61
- if (DiskSpace && DiskSpace !== EFlag.Grey) {
62
- usedSpaceFlag += FLAGS_POINTS[DiskSpace];
63
- }
64
-
65
- const available = Number(AvailableSize ?? PDisk?.AvailableSize) || 0;
66
- const allocated = Number(AllocatedSize) || 0;
67
-
68
- usedSpaceBytes += allocated;
69
- limitSizeBytes += available + allocated;
70
-
71
- readSpeedBytesPerSec += Number(ReadThroughput) || 0;
72
- writeSpeedBytesPerSec += Number(WriteThroughput) || 0;
73
-
74
- const currentType = getPDiskType(PDisk || {});
75
- mediaType =
76
- currentType && (currentType === mediaType || mediaType === '')
77
- ? currentType
78
- : 'Mixed';
79
- }
80
- }
81
-
82
- // VDisk doesn't have its own StoragePoolName when located inside StoragePool data
83
- const vDisks = group.VDisks?.map((vdisk) => ({
84
- ...vdisk,
85
- StoragePoolName: poolName,
86
- Donors: vdisk.Donors?.map((donor) => ({
87
- ...donor,
88
- StoragePoolName: poolName,
89
- })),
90
- }));
91
-
92
- return {
93
- ...group,
94
- VDisks: vDisks,
95
- Read: readSpeedBytesPerSec,
96
- Write: writeSpeedBytesPerSec,
97
- PoolName: poolName,
98
- Used: usedSpaceBytes,
99
- Limit: limitSizeBytes,
100
- Missing: missing,
101
- UsedSpaceFlag: usedSpaceFlag,
102
- Type: mediaType || null,
103
- };
104
- };
105
-
106
- const prepareStorageNodeData = (node: TNodeInfo): PreparedStorageNode => {
107
- const systemState = node.SystemState ?? {};
108
- const missing =
109
- node.PDisks?.filter((pDisk) => {
110
- return pDisk.State !== TPDiskState.Normal;
111
- }).length || 0;
112
-
113
- return {
114
- NodeId: node.NodeId,
115
- SystemState: systemState.SystemState,
116
- DataCenter: systemState.DataCenter,
117
- Rack: systemState.Rack,
118
- Host: systemState.Host,
119
- Endpoints: systemState.Endpoints,
120
- Uptime: calcUptime(systemState.StartTime),
121
- StartTime: systemState.StartTime,
122
- PDisks: node.PDisks,
123
- Missing: missing,
124
- };
125
- };
17
+ import {VISIBLE_ENTITIES} from './constants';
126
18
 
127
19
  // ==== Filters ====
128
20
 
@@ -163,24 +55,21 @@ const filterGroupsByUsage = (entities: PreparedStorageGroup[], usage?: string[])
163
55
  }
164
56
 
165
57
  return entities.filter((entity) => {
166
- const entityUsage = getUsage(entity, 5);
58
+ const entityUsage = entity.Usage;
167
59
  return usage.some((val) => Number(val) <= entityUsage && entityUsage < Number(val) + 5);
168
60
  });
169
61
  };
170
62
 
171
63
  // ==== Simple selectors ====
172
64
 
173
- export const selectStoragePools = (state: StorageStateSlice) => state.storage.groups?.StoragePools;
174
- export const selectStorageGroupsCount = (state: StorageStateSlice) => ({
175
- total: state.storage.groups?.TotalGroups || 0,
176
- found: state.storage.groups?.FoundGroups || 0,
177
- });
178
- export const selectStorageNodes = (state: StorageStateSlice) => state.storage.nodes?.Nodes;
179
- export const selectStorageNodesCount = (state: StorageStateSlice) => ({
180
- total: state.storage.nodes?.TotalNodes || 0,
181
- found: state.storage.nodes?.FoundNodes || 0,
65
+ export const selectEntitiesCount = (state: StorageStateSlice) => ({
66
+ total: state.storage.total,
67
+ found: state.storage.found,
182
68
  });
183
69
 
70
+ export const selectStorageGroups = (state: StorageStateSlice) => state.storage.groups;
71
+ export const selectStorageNodes = (state: StorageStateSlice) => state.storage.nodes;
72
+
184
73
  export const selectStorageFilter = (state: StorageStateSlice) => state.storage.filter;
185
74
  export const selectUsageFilter = (state: StorageStateSlice) => state.storage.usageFilter;
186
75
  export const selectVisibleEntities = (state: StorageStateSlice) => state.storage.visible;
@@ -188,29 +77,39 @@ export const selectNodesUptimeFilter = (state: StorageStateSlice) =>
188
77
  state.storage.nodesUptimeFilter;
189
78
  export const selectStorageType = (state: StorageStateSlice) => state.storage.type;
190
79
 
191
- // ==== Complex selectors ====
80
+ // ==== Sort params selectors ====
81
+ export const selectNodesSortParams = (state: StorageStateSlice) => {
82
+ const defaultSortValue: NodesSortValue = NODES_SORT_VALUES.NodeId;
83
+ const defaultSortOrder: OrderType = ASCENDING;
84
+
85
+ return {
86
+ sortValue: state.storage.nodesSortValue || defaultSortValue,
87
+ sortOrder: state.storage.nodesSortOrder || defaultSortOrder,
88
+ };
89
+ };
192
90
 
193
- const selectPreparedStorageNodes: Selector<StorageStateSlice, PreparedStorageNode[]> =
194
- createSelector(selectStorageNodes, (storageNodes) => {
195
- if (!storageNodes) {
196
- return [];
197
- }
91
+ export const selectGroupsSortParams = (state: StorageStateSlice) => {
92
+ const visibleEntities = state.storage.visible;
198
93
 
199
- return storageNodes.map(prepareStorageNodeData);
200
- });
94
+ let defaultSortValue: StorageSortValue = STORAGE_SORT_VALUES.PoolName;
95
+ let defaultSortOrder: OrderType = ASCENDING;
201
96
 
202
- export const selectPreparedStorageGroups: Selector<StorageStateSlice, PreparedStorageGroup[]> =
203
- createSelector(selectStoragePools, (storagePools) => {
204
- const preparedGroups: PreparedStorageGroup[] = [];
97
+ if (visibleEntities === VISIBLE_ENTITIES.missing) {
98
+ defaultSortValue = STORAGE_SORT_VALUES.Degraded;
99
+ defaultSortOrder = DESCENDING;
100
+ }
205
101
 
206
- storagePools?.forEach((pool) => {
207
- pool.Groups?.forEach((group) => {
208
- preparedGroups.push(prepareStorageGroupData(group, pool.Name));
209
- });
210
- });
102
+ if (visibleEntities === VISIBLE_ENTITIES.space) {
103
+ defaultSortValue = STORAGE_SORT_VALUES.Usage;
104
+ defaultSortOrder = DESCENDING;
105
+ }
211
106
 
212
- return preparedGroups;
213
- });
107
+ return {
108
+ sortValue: state.storage.groupsSortValue || defaultSortValue,
109
+ sortOrder: state.storage.groupsSortOrder || defaultSortOrder,
110
+ };
111
+ };
112
+ // ==== Complex selectors ====
214
113
 
215
114
  export const selectVDisksForPDisk: Selector<
216
115
  StorageStateSlice,
@@ -232,11 +131,12 @@ export const selectVDisksForPDisk: Selector<
232
131
  );
233
132
 
234
133
  export const selectUsageFilterOptions: Selector<StorageStateSlice, UsageFilter[]> = createSelector(
235
- selectPreparedStorageGroups,
134
+ selectStorageGroups,
236
135
  (groups) => {
237
136
  const items: Record<number, number> = {};
238
137
 
239
- groups.forEach((group) => {
138
+ groups?.forEach((group) => {
139
+ // Get groups usage with step 5
240
140
  const usage = getUsage(group, 5);
241
141
 
242
142
  if (!Object.prototype.hasOwnProperty.call(items, usage)) {
@@ -256,9 +156,9 @@ export const selectUsageFilterOptions: Selector<StorageStateSlice, UsageFilter[]
256
156
 
257
157
  export const selectFilteredNodes: Selector<StorageStateSlice, PreparedStorageNode[]> =
258
158
  createSelector(
259
- [selectPreparedStorageNodes, selectStorageFilter, selectNodesUptimeFilter],
159
+ [selectStorageNodes, selectStorageFilter, selectNodesUptimeFilter],
260
160
  (storageNodes, textFilter, uptimeFilter) => {
261
- let result = storageNodes;
161
+ let result = storageNodes || [];
262
162
  result = filterNodesByText(result, textFilter);
263
163
  result = filterNodesByUptime(result, uptimeFilter);
264
164
 
@@ -268,9 +168,9 @@ export const selectFilteredNodes: Selector<StorageStateSlice, PreparedStorageNod
268
168
 
269
169
  export const selectFilteredGroups: Selector<StorageStateSlice, PreparedStorageGroup[]> =
270
170
  createSelector(
271
- [selectPreparedStorageGroups, selectStorageFilter, selectUsageFilter],
171
+ [selectStorageGroups, selectStorageFilter, selectUsageFilter],
272
172
  (storageGroups, textFilter, usageFilter) => {
273
- let result = storageGroups;
173
+ let result = storageGroups || [];
274
174
  result = filterGroupsByText(result, textFilter);
275
175
  result = filterGroupsByUsage(result, usageFilter);
276
176