ydb-embedded-ui 4.14.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.
- package/CHANGELOG.md +9 -0
- package/dist/containers/App/App.js +1 -1
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
- package/dist/containers/Authentication/Authentication.tsx +1 -1
- package/dist/containers/Storage/Storage.tsx +64 -32
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +56 -73
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +33 -43
- package/dist/containers/Storage/utils/index.ts +3 -3
- package/dist/containers/Tenant/i18n/en.json +3 -0
- package/dist/containers/Tenant/i18n/ru.json +3 -0
- package/dist/containers/Tenant/utils/queryTemplates.ts +89 -0
- package/dist/containers/Tenant/utils/schema.ts +1 -1
- package/dist/containers/Tenant/utils/schemaActions.ts +27 -44
- package/dist/containers/UserSettings/i18n/en.json +1 -1
- package/dist/containers/UserSettings/i18n/ru.json +1 -1
- package/dist/{reportWebVitals.js → reportWebVitals.ts} +3 -1
- package/dist/services/api.ts +6 -4
- package/dist/store/reducers/{authentication.js → authentication/authentication.ts} +14 -7
- package/dist/store/reducers/authentication/types.ts +15 -0
- package/dist/store/reducers/describe.ts +1 -16
- package/dist/store/reducers/index.ts +1 -1
- package/dist/store/reducers/storage/selectors.ts +50 -150
- package/dist/store/reducers/storage/storage.ts +73 -25
- package/dist/store/reducers/storage/types.ts +49 -17
- package/dist/store/reducers/storage/utils.ts +207 -0
- package/dist/store/utils.ts +1 -1
- package/dist/types/api/error.ts +4 -0
- package/dist/types/api/storage.ts +32 -4
- package/dist/types/window.d.ts +1 -0
- package/dist/utils/hooks/index.ts +1 -0
- package/dist/utils/hooks/useStorageRequestParams.ts +28 -0
- package/dist/utils/hooks/useTableSort.ts +1 -1
- package/dist/utils/storage.ts +31 -3
- package/package.json +1 -5
- package/dist/HOCS/WithSearch/WithSearch.js +0 -26
- package/dist/HOCS/index.js +0 -1
- package/dist/components/Hotkey/Hotkey.js +0 -102
- package/dist/components/Pagination/Pagination.js +0 -63
- package/dist/components/Pagination/Pagination.scss +0 -28
- package/dist/types/store/storage.ts +0 -12
- /package/dist/{index.js → index.tsx} +0 -0
- /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]: '
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
[
|
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:
|
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
|
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": "Добавляет фильтрацию и сортировку
|
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
|
-
|
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);
|
package/dist/services/api.ts
CHANGED
@@ -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 {
|
2
|
-
|
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
|
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 {
|
5
|
-
import {
|
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 {
|
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 =
|
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
|
174
|
-
|
175
|
-
|
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
|
-
// ====
|
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
|
194
|
-
|
195
|
-
if (!storageNodes) {
|
196
|
-
return [];
|
197
|
-
}
|
91
|
+
export const selectGroupsSortParams = (state: StorageStateSlice) => {
|
92
|
+
const visibleEntities = state.storage.visible;
|
198
93
|
|
199
|
-
|
200
|
-
|
94
|
+
let defaultSortValue: StorageSortValue = STORAGE_SORT_VALUES.PoolName;
|
95
|
+
let defaultSortOrder: OrderType = ASCENDING;
|
201
96
|
|
202
|
-
|
203
|
-
|
204
|
-
|
97
|
+
if (visibleEntities === VISIBLE_ENTITIES.missing) {
|
98
|
+
defaultSortValue = STORAGE_SORT_VALUES.Degraded;
|
99
|
+
defaultSortOrder = DESCENDING;
|
100
|
+
}
|
205
101
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
});
|
102
|
+
if (visibleEntities === VISIBLE_ENTITIES.space) {
|
103
|
+
defaultSortValue = STORAGE_SORT_VALUES.Usage;
|
104
|
+
defaultSortOrder = DESCENDING;
|
105
|
+
}
|
211
106
|
|
212
|
-
|
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
|
-
|
134
|
+
selectStorageGroups,
|
236
135
|
(groups) => {
|
237
136
|
const items: Record<number, number> = {};
|
238
137
|
|
239
|
-
groups
|
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
|
-
[
|
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
|
-
[
|
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
|
|