ydb-embedded-ui 4.14.0 → 4.15.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +16 -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 +113 -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,113 @@
|
|
1
|
+
export const createTableTemplate = (path: string) => {
|
2
|
+
return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/create_table
|
3
|
+
CREATE TABLE \`${path}/ydb_row_table\` (
|
4
|
+
category_id Uint64 NOT NULL,
|
5
|
+
id Uint64,
|
6
|
+
expire_at Datetime,
|
7
|
+
updated_on Datetime,
|
8
|
+
name Text,
|
9
|
+
\`binary-payload\` Bytes,
|
10
|
+
attributes JsonDocument,
|
11
|
+
-- uncomment to add a secondary index
|
12
|
+
-- INDEX idx_row_table_id GLOBAL SYNC ON ( id ) COVER ( name, attributes ), -- Secondary indexes docs https://ydb.tech/en/docs/yql/reference/syntax/create_table#secondary_index
|
13
|
+
PRIMARY KEY (category_id, id)
|
14
|
+
)
|
15
|
+
WITH (
|
16
|
+
AUTO_PARTITIONING_BY_SIZE = ENABLED,
|
17
|
+
AUTO_PARTITIONING_PARTITION_SIZE_MB = 2048,
|
18
|
+
AUTO_PARTITIONING_BY_LOAD = ENABLED,
|
19
|
+
AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 4,
|
20
|
+
AUTO_PARTITIONING_MAX_PARTITIONS_COUNT = 1024,
|
21
|
+
-- uncomment to create a table with predefined partitions
|
22
|
+
-- UNIFORM_PARTITIONS = 4, -- The number of partitions for uniform initial table partitioning.
|
23
|
+
-- The primary key's first column must have type Uint64 or Uint32.
|
24
|
+
-- A created table is immediately divided into the specified number of partitions
|
25
|
+
-- uncomment to launch read only replicas in every AZ
|
26
|
+
-- READ_REPLICAS_SETTINGS = 'PER_AZ:1', -- Enable read replicas for stale read, launch one replica in every availability zone
|
27
|
+
-- uncomment to enable ttl
|
28
|
+
-- TTL = Interval("PT1H") ON expire_at, -- Enable background deletion of expired rows https://ydb.tech/en/docs/concepts/ttl
|
29
|
+
KEY_BLOOM_FILTER = ENABLED -- With a Bloom filter, you can more efficiently determine
|
30
|
+
-- if some keys are missing in a table when making multiple single queries by the primary key.
|
31
|
+
)`;
|
32
|
+
};
|
33
|
+
export const alterTableTemplate = (path: string) => {
|
34
|
+
return `ALTER TABLE \`${path}\`
|
35
|
+
ADD COLUMN is_deleted Bool;`;
|
36
|
+
};
|
37
|
+
export const selectQueryTemplate = (path: string) => {
|
38
|
+
return `SELECT *
|
39
|
+
FROM \`${path}\`
|
40
|
+
LIMIT 10;`;
|
41
|
+
};
|
42
|
+
export const upsertQueryTemplate = (path: string) => {
|
43
|
+
return `UPSERT INTO \`${path}\`
|
44
|
+
( \`id\`, \`name\` )
|
45
|
+
VALUES ( );`;
|
46
|
+
};
|
47
|
+
|
48
|
+
export const dropExternalTableTemplate = (path: string) => {
|
49
|
+
return `DROP EXTERNAL TABLE \`${path}\`;`;
|
50
|
+
};
|
51
|
+
|
52
|
+
export const createExternalTableTemplate = (path: string) => {
|
53
|
+
// Remove data source name from path
|
54
|
+
// to create table in the same folder with data source
|
55
|
+
const targetPath = path.split('/').slice(0, -1).join('/');
|
56
|
+
|
57
|
+
return `CREATE EXTERNAL TABLE \`${targetPath}/my_external_table\` (
|
58
|
+
column1 Int,
|
59
|
+
column2 Int
|
60
|
+
) WITH (
|
61
|
+
DATA_SOURCE="${path}",
|
62
|
+
LOCATION="",
|
63
|
+
FORMAT="json_as_string",
|
64
|
+
\`file_pattern\`=""
|
65
|
+
);`;
|
66
|
+
};
|
67
|
+
|
68
|
+
export const createTopicTemplate = (path: string) => {
|
69
|
+
return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/create_topic
|
70
|
+
CREATE TOPIC \`${path}/my_topic\` (
|
71
|
+
CONSUMER consumer1,
|
72
|
+
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.
|
73
|
+
-- Value type: Datetime OR Timestamp OR integer (unix-timestamp in the numeric format).
|
74
|
+
-- Default value: now
|
75
|
+
) WITH (
|
76
|
+
min_active_partitions = 5, -- Minimum number of topic partitions.
|
77
|
+
partition_count_limit = 10, -- Maximum number of active partitions in the topic. 0 is interpreted as unlimited.
|
78
|
+
retention_period = Interval('PT12H'), -- Data retention period in the topic. Value type: Interval, default value: 18h.
|
79
|
+
retention_storage_mb = 1, -- Limit on the maximum disk space occupied by the topic data.
|
80
|
+
-- When this value is exceeded, the older data is cleared, like under a retention policy.
|
81
|
+
-- 0 is interpreted as unlimited.
|
82
|
+
partition_write_speed_bytes_per_second = 2097152, -- Maximum allowed write speed per partition.
|
83
|
+
partition_write_burst_bytes = 2097152 -- Write quota allocated for write bursts.
|
84
|
+
-- When set to zero, the actual write_burst value is equalled to
|
85
|
+
-- the quota value (this allows write bursts of up to one second).
|
86
|
+
);`;
|
87
|
+
};
|
88
|
+
|
89
|
+
export const alterTopicTemplate = (path: string) => {
|
90
|
+
return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/alter_topic
|
91
|
+
ALTER TOPIC \`${path}\`
|
92
|
+
ADD CONSUMER new_consumer WITH (read_from = 0), -- Sets up the message write time starting from which the consumer will receive data.
|
93
|
+
-- Value type: Datetime OR Timestamp OR integer (unix-timestamp in the numeric format).
|
94
|
+
-- Default value: now
|
95
|
+
ALTER CONSUMER consumer1 SET (read_from = Datetime('2023-12-01T12:13:22Z')),
|
96
|
+
DROP CONSUMER consumer2,
|
97
|
+
SET (
|
98
|
+
min_active_partitions = 10, -- Minimum number of topic partitions.
|
99
|
+
partition_count_limit = 15, -- Maximum number of active partitions in the topic. 0 is interpreted as unlimited.
|
100
|
+
retention_period = Interval('PT36H'), -- Data retention period in the topic. Value type: Interval, default value: 18h.
|
101
|
+
retention_storage_mb = 10, -- Limit on the maximum disk space occupied by the topic data.
|
102
|
+
-- When this value is exceeded, the older data is cleared, like under a retention policy.
|
103
|
+
-- 0 is interpreted as unlimited.
|
104
|
+
partition_write_speed_bytes_per_second = 3145728, -- Maximum allowed write speed per partition.
|
105
|
+
partition_write_burst_bytes = 1048576 -- Write quota allocated for write bursts.
|
106
|
+
-- When set to zero, the actual write_burst value is equalled to
|
107
|
+
-- the quota value (this allows write bursts of up to one second).
|
108
|
+
);`;
|
109
|
+
};
|
110
|
+
|
111
|
+
export const dropTopicTemplate = (path: string) => {
|
112
|
+
return `DROP TOPIC \`${path}\`;`;
|
113
|
+
};
|
@@ -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
|
|