ydb-embedded-ui 4.9.0 → 4.10.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 +12 -0
- package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +7 -4
- package/dist/components/EntityStatus/EntityStatus.js +3 -1
- package/dist/components/FormattedBytes/FormattedBytes.tsx +10 -0
- package/dist/components/FormattedBytes/utils.tsx +13 -0
- package/dist/components/FullNodeViewer/FullNodeViewer.tsx +73 -0
- package/dist/components/InfoViewer/formatters/table.ts +6 -5
- package/dist/components/ProblemFilter/ProblemFilter.tsx +2 -2
- package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +4 -4
- package/dist/components/TruncatedQuery/{TruncatedQuery.js → TruncatedQuery.tsx} +10 -8
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +6 -6
- package/dist/containers/Cluster/Cluster.tsx +10 -6
- package/dist/containers/Node/Node.tsx +3 -3
- package/dist/containers/Nodes/Nodes.tsx +2 -2
- package/dist/containers/Storage/PDisk/PDisk.tsx +2 -7
- package/dist/containers/Storage/Storage.tsx +240 -0
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +45 -40
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +12 -16
- package/dist/containers/Storage/UsageFilter/UsageFilter.scss +1 -0
- package/dist/containers/Storage/UsageFilter/UsageFilter.tsx +17 -17
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +7 -4
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +0 -15
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +10 -3
- package/dist/containers/Tenant/{Preview → Query/Preview}/Preview.scss +1 -1
- package/dist/containers/Tenant/Query/Preview/Preview.tsx +121 -0
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +1 -1
- package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +6 -8
- package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +1 -1
- package/dist/containers/Tenant/Query/i18n/en.json +8 -1
- package/dist/containers/Tenant/Query/i18n/ru.json +8 -1
- package/dist/containers/Tenants/Tenants.tsx +269 -0
- package/dist/services/api.ts +8 -3
- package/dist/store/reducers/nodes/nodes.ts +4 -4
- package/dist/store/reducers/partitions/types.ts +3 -3
- package/dist/store/reducers/settings/settings.ts +4 -2
- package/dist/store/reducers/settings/types.ts +3 -1
- package/dist/store/reducers/storage/selectors.ts +279 -0
- package/dist/store/reducers/storage/storage.ts +191 -0
- package/dist/store/reducers/storage/types.ts +80 -0
- package/dist/store/reducers/tenants/selectors.ts +46 -0
- package/dist/store/reducers/tenants/tenants.ts +21 -14
- package/dist/store/reducers/tenants/types.ts +20 -5
- package/dist/store/reducers/tenants/utils.ts +68 -0
- package/dist/types/additionalProps.ts +8 -0
- package/dist/types/api/storage.ts +1 -1
- package/dist/types/store/topic.ts +3 -3
- package/dist/utils/bytesParsers/__test__/formatBytes.test.ts +38 -0
- package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +2 -2
- package/dist/utils/bytesParsers/formatBytes.ts +132 -0
- package/dist/utils/bytesParsers/i18n/en.json +1 -0
- package/dist/utils/bytesParsers/i18n/ru.json +1 -0
- package/dist/utils/bytesParsers/index.ts +1 -1
- package/dist/utils/index.js +5 -10
- package/dist/utils/numeral.ts +8 -0
- package/package.json +1 -1
- package/dist/components/FullNodeViewer/FullNodeViewer.js +0 -89
- package/dist/containers/Node/NodeOverview/NodeOverview.scss +0 -0
- package/dist/containers/Node/NodeOverview/NodeOverview.tsx +0 -21
- package/dist/containers/Storage/Storage.js +0 -350
- package/dist/containers/Tenant/Preview/Preview.js +0 -168
- package/dist/containers/Tenants/Tenants.js +0 -363
- package/dist/store/reducers/storage/storage.js +0 -404
- package/dist/utils/bytesParsers/formatBytesCustom.ts +0 -57
@@ -0,0 +1,279 @@
|
|
1
|
+
import {Selector, createSelector} from 'reselect';
|
2
|
+
import {getUsage} from '../../../utils/storage';
|
3
|
+
|
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';
|
10
|
+
|
11
|
+
import type {
|
12
|
+
PreparedStorageGroup,
|
13
|
+
PreparedStorageNode,
|
14
|
+
RawStorageGroup,
|
15
|
+
StorageStateSlice,
|
16
|
+
UsageFilter,
|
17
|
+
} from './types';
|
18
|
+
import {filterNodesByUptime} from '../nodes/nodes';
|
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
|
+
};
|
126
|
+
|
127
|
+
// ==== Filters ====
|
128
|
+
|
129
|
+
const prepareSearchText = (text: string) => text.trim().toLowerCase();
|
130
|
+
|
131
|
+
const filterNodesByText = (entities: PreparedStorageNode[], text: string) => {
|
132
|
+
const preparedSearch = prepareSearchText(text);
|
133
|
+
|
134
|
+
if (!preparedSearch) {
|
135
|
+
return entities;
|
136
|
+
}
|
137
|
+
|
138
|
+
return entities.filter((entity) => {
|
139
|
+
return (
|
140
|
+
entity.NodeId?.toString().includes(preparedSearch) ||
|
141
|
+
entity.Host?.toLowerCase().includes(preparedSearch)
|
142
|
+
);
|
143
|
+
});
|
144
|
+
};
|
145
|
+
const filterGroupsByText = (entities: PreparedStorageGroup[], text: string) => {
|
146
|
+
const preparedSearch = prepareSearchText(text);
|
147
|
+
|
148
|
+
if (!preparedSearch) {
|
149
|
+
return entities;
|
150
|
+
}
|
151
|
+
|
152
|
+
return entities.filter((entity) => {
|
153
|
+
return (
|
154
|
+
entity.PoolName?.toLowerCase().includes(preparedSearch) ||
|
155
|
+
entity.GroupID?.toString().includes(preparedSearch)
|
156
|
+
);
|
157
|
+
});
|
158
|
+
};
|
159
|
+
|
160
|
+
const filterGroupsByUsage = (entities: PreparedStorageGroup[], usage?: string[]) => {
|
161
|
+
if (!Array.isArray(usage) || usage.length === 0) {
|
162
|
+
return entities;
|
163
|
+
}
|
164
|
+
|
165
|
+
return entities.filter((entity) => {
|
166
|
+
const entityUsage = getUsage(entity, 5);
|
167
|
+
return usage.some((val) => Number(val) <= entityUsage && entityUsage < Number(val) + 5);
|
168
|
+
});
|
169
|
+
};
|
170
|
+
|
171
|
+
// ==== Simple selectors ====
|
172
|
+
|
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,
|
182
|
+
});
|
183
|
+
|
184
|
+
export const selectStorageFilter = (state: StorageStateSlice) => state.storage.filter;
|
185
|
+
export const selectUsageFilter = (state: StorageStateSlice) => state.storage.usageFilter;
|
186
|
+
export const selectVisibleEntities = (state: StorageStateSlice) => state.storage.visible;
|
187
|
+
export const selectNodesUptimeFilter = (state: StorageStateSlice) =>
|
188
|
+
state.storage.nodesUptimeFilter;
|
189
|
+
export const selectStorageType = (state: StorageStateSlice) => state.storage.type;
|
190
|
+
|
191
|
+
// ==== Complex selectors ====
|
192
|
+
|
193
|
+
const selectPreparedStorageNodes: Selector<StorageStateSlice, PreparedStorageNode[]> =
|
194
|
+
createSelector(selectStorageNodes, (storageNodes) => {
|
195
|
+
if (!storageNodes) {
|
196
|
+
return [];
|
197
|
+
}
|
198
|
+
|
199
|
+
return storageNodes.map(prepareStorageNodeData);
|
200
|
+
});
|
201
|
+
|
202
|
+
export const selectPreparedStorageGroups: Selector<StorageStateSlice, PreparedStorageGroup[]> =
|
203
|
+
createSelector(selectStoragePools, (storagePools) => {
|
204
|
+
const preparedGroups: PreparedStorageGroup[] = [];
|
205
|
+
|
206
|
+
storagePools?.forEach((pool) => {
|
207
|
+
pool.Groups?.forEach((group) => {
|
208
|
+
preparedGroups.push(prepareStorageGroupData(group, pool.Name));
|
209
|
+
});
|
210
|
+
});
|
211
|
+
|
212
|
+
return preparedGroups;
|
213
|
+
});
|
214
|
+
|
215
|
+
export const selectVDisksForPDisk: Selector<
|
216
|
+
StorageStateSlice,
|
217
|
+
TVDiskStateInfo[] | undefined,
|
218
|
+
[number | undefined, number | undefined]
|
219
|
+
> = createSelector(
|
220
|
+
[
|
221
|
+
selectStorageNodes,
|
222
|
+
(_state, nodeId: number | undefined) => nodeId,
|
223
|
+
(_state, _nodeId, pdiskId: number | undefined) => pdiskId,
|
224
|
+
],
|
225
|
+
(storageNodes, nodeId, pdiskId) => {
|
226
|
+
const targetNode = storageNodes?.find((node) => node.NodeId === nodeId);
|
227
|
+
return targetNode?.VDisks?.filter((vdisk) => vdisk.PDiskId === pdiskId).map((data) => ({
|
228
|
+
...data,
|
229
|
+
NodeId: nodeId,
|
230
|
+
}));
|
231
|
+
},
|
232
|
+
);
|
233
|
+
|
234
|
+
export const selectUsageFilterOptions: Selector<StorageStateSlice, UsageFilter[]> = createSelector(
|
235
|
+
selectPreparedStorageGroups,
|
236
|
+
(groups) => {
|
237
|
+
const items: Record<number, number> = {};
|
238
|
+
|
239
|
+
groups.forEach((group) => {
|
240
|
+
const usage = getUsage(group, 5);
|
241
|
+
|
242
|
+
if (!Object.prototype.hasOwnProperty.call(items, usage)) {
|
243
|
+
items[usage] = 0;
|
244
|
+
}
|
245
|
+
|
246
|
+
items[usage] += 1;
|
247
|
+
});
|
248
|
+
|
249
|
+
return Object.entries(items)
|
250
|
+
.map(([threshold, count]) => ({threshold: Number(threshold), count}))
|
251
|
+
.sort((a, b) => b.threshold - a.threshold);
|
252
|
+
},
|
253
|
+
);
|
254
|
+
|
255
|
+
// ==== Complex selectors with filters ====
|
256
|
+
|
257
|
+
export const selectFilteredNodes: Selector<StorageStateSlice, PreparedStorageNode[]> =
|
258
|
+
createSelector(
|
259
|
+
[selectPreparedStorageNodes, selectStorageFilter, selectNodesUptimeFilter],
|
260
|
+
(storageNodes, textFilter, uptimeFilter) => {
|
261
|
+
let result = storageNodes;
|
262
|
+
result = filterNodesByText(result, textFilter);
|
263
|
+
result = filterNodesByUptime(result, uptimeFilter);
|
264
|
+
|
265
|
+
return result;
|
266
|
+
},
|
267
|
+
);
|
268
|
+
|
269
|
+
export const selectFilteredGroups: Selector<StorageStateSlice, PreparedStorageGroup[]> =
|
270
|
+
createSelector(
|
271
|
+
[selectPreparedStorageGroups, selectStorageFilter, selectUsageFilter],
|
272
|
+
(storageGroups, textFilter, usageFilter) => {
|
273
|
+
let result = storageGroups;
|
274
|
+
result = filterGroupsByText(result, textFilter);
|
275
|
+
result = filterGroupsByUsage(result, usageFilter);
|
276
|
+
|
277
|
+
return result;
|
278
|
+
},
|
279
|
+
);
|
@@ -0,0 +1,191 @@
|
|
1
|
+
import type {Reducer} from 'redux';
|
2
|
+
import _ from 'lodash';
|
3
|
+
|
4
|
+
import {NodesUptimeFilterValues} from '../../../utils/nodes';
|
5
|
+
import '../../../services/api';
|
6
|
+
|
7
|
+
import {createRequestActionTypes, createApiRequest} from '../../utils';
|
8
|
+
|
9
|
+
import type {StorageAction, StorageState, StorageType, VisibleEntities} from './types';
|
10
|
+
import {VISIBLE_ENTITIES, STORAGE_TYPES} from './constants';
|
11
|
+
|
12
|
+
export const FETCH_STORAGE = createRequestActionTypes('storage', 'FETCH_STORAGE');
|
13
|
+
|
14
|
+
const SET_INITIAL = 'storage/SET_INITIAL';
|
15
|
+
const SET_FILTER = 'storage/SET_FILTER';
|
16
|
+
const SET_USAGE_FILTER = 'storage/SET_USAGE_FILTER';
|
17
|
+
const SET_VISIBLE_GROUPS = 'storage/SET_VISIBLE_GROUPS';
|
18
|
+
const SET_STORAGE_TYPE = 'storage/SET_STORAGE_TYPE';
|
19
|
+
const SET_NODES_UPTIME_FILTER = 'storage/SET_NODES_UPTIME_FILTER';
|
20
|
+
const SET_DATA_WAS_NOT_LOADED = 'storage/SET_DATA_WAS_NOT_LOADED';
|
21
|
+
|
22
|
+
const initialState = {
|
23
|
+
loading: true,
|
24
|
+
wasLoaded: false,
|
25
|
+
filter: '',
|
26
|
+
usageFilter: [],
|
27
|
+
visible: VISIBLE_ENTITIES.missing,
|
28
|
+
nodesUptimeFilter: NodesUptimeFilterValues.All,
|
29
|
+
type: STORAGE_TYPES.groups,
|
30
|
+
};
|
31
|
+
|
32
|
+
const storage: Reducer<StorageState, StorageAction> = (state = initialState, action) => {
|
33
|
+
switch (action.type) {
|
34
|
+
case FETCH_STORAGE.REQUEST: {
|
35
|
+
return {
|
36
|
+
...state,
|
37
|
+
loading: true,
|
38
|
+
};
|
39
|
+
}
|
40
|
+
case FETCH_STORAGE.SUCCESS: {
|
41
|
+
return {
|
42
|
+
...state,
|
43
|
+
nodes: action.data.nodes,
|
44
|
+
groups: action.data.groups,
|
45
|
+
loading: false,
|
46
|
+
wasLoaded: true,
|
47
|
+
error: undefined,
|
48
|
+
};
|
49
|
+
}
|
50
|
+
case FETCH_STORAGE.FAILURE: {
|
51
|
+
if (action.error?.isCancelled) {
|
52
|
+
return state;
|
53
|
+
}
|
54
|
+
|
55
|
+
return {
|
56
|
+
...state,
|
57
|
+
error: action.error,
|
58
|
+
loading: false,
|
59
|
+
wasLoaded: true,
|
60
|
+
};
|
61
|
+
}
|
62
|
+
case SET_INITIAL: {
|
63
|
+
return {
|
64
|
+
...initialState,
|
65
|
+
};
|
66
|
+
}
|
67
|
+
case SET_FILTER: {
|
68
|
+
return {
|
69
|
+
...state,
|
70
|
+
filter: action.data,
|
71
|
+
};
|
72
|
+
}
|
73
|
+
case SET_USAGE_FILTER: {
|
74
|
+
return {
|
75
|
+
...state,
|
76
|
+
usageFilter: action.data,
|
77
|
+
};
|
78
|
+
}
|
79
|
+
case SET_VISIBLE_GROUPS: {
|
80
|
+
return {
|
81
|
+
...state,
|
82
|
+
visible: action.data,
|
83
|
+
wasLoaded: false,
|
84
|
+
error: undefined,
|
85
|
+
};
|
86
|
+
}
|
87
|
+
|
88
|
+
case SET_NODES_UPTIME_FILTER: {
|
89
|
+
return {
|
90
|
+
...state,
|
91
|
+
nodesUptimeFilter: action.data,
|
92
|
+
};
|
93
|
+
}
|
94
|
+
case SET_STORAGE_TYPE: {
|
95
|
+
return {
|
96
|
+
...state,
|
97
|
+
type: action.data,
|
98
|
+
filter: '',
|
99
|
+
usageFilter: [],
|
100
|
+
wasLoaded: false,
|
101
|
+
error: undefined,
|
102
|
+
};
|
103
|
+
}
|
104
|
+
case SET_DATA_WAS_NOT_LOADED: {
|
105
|
+
return {
|
106
|
+
...state,
|
107
|
+
wasLoaded: false,
|
108
|
+
};
|
109
|
+
}
|
110
|
+
default:
|
111
|
+
return state;
|
112
|
+
}
|
113
|
+
};
|
114
|
+
|
115
|
+
export function getStorageInfo(
|
116
|
+
{
|
117
|
+
tenant,
|
118
|
+
visibleEntities,
|
119
|
+
nodeId,
|
120
|
+
type,
|
121
|
+
}: {
|
122
|
+
tenant?: string;
|
123
|
+
visibleEntities?: VisibleEntities;
|
124
|
+
nodeId?: string;
|
125
|
+
type?: StorageType;
|
126
|
+
},
|
127
|
+
{concurrentId}: {concurrentId?: string} = {},
|
128
|
+
) {
|
129
|
+
if (type === STORAGE_TYPES.nodes) {
|
130
|
+
return createApiRequest({
|
131
|
+
request: window.api.getNodes({tenant, visibleEntities, type: 'static'}, {concurrentId}),
|
132
|
+
actions: FETCH_STORAGE,
|
133
|
+
dataHandler: (data) => ({nodes: data}),
|
134
|
+
});
|
135
|
+
}
|
136
|
+
|
137
|
+
return createApiRequest({
|
138
|
+
request: window.api.getStorageInfo({tenant, visibleEntities, nodeId}, {concurrentId}),
|
139
|
+
actions: FETCH_STORAGE,
|
140
|
+
dataHandler: (data) => ({groups: data}),
|
141
|
+
});
|
142
|
+
}
|
143
|
+
|
144
|
+
export function setInitialState() {
|
145
|
+
return {
|
146
|
+
type: SET_INITIAL,
|
147
|
+
} as const;
|
148
|
+
}
|
149
|
+
|
150
|
+
export function setStorageType(value: StorageType) {
|
151
|
+
return {
|
152
|
+
type: SET_STORAGE_TYPE,
|
153
|
+
data: value,
|
154
|
+
} as const;
|
155
|
+
}
|
156
|
+
|
157
|
+
export function setStorageTextFilter(value: string) {
|
158
|
+
return {
|
159
|
+
type: SET_FILTER,
|
160
|
+
data: value,
|
161
|
+
} as const;
|
162
|
+
}
|
163
|
+
|
164
|
+
export function setUsageFilter(value: string[]) {
|
165
|
+
return {
|
166
|
+
type: SET_USAGE_FILTER,
|
167
|
+
data: value,
|
168
|
+
} as const;
|
169
|
+
}
|
170
|
+
|
171
|
+
export function setVisibleEntities(value: VisibleEntities) {
|
172
|
+
return {
|
173
|
+
type: SET_VISIBLE_GROUPS,
|
174
|
+
data: value,
|
175
|
+
} as const;
|
176
|
+
}
|
177
|
+
|
178
|
+
export function setNodesUptimeFilter(value: NodesUptimeFilterValues) {
|
179
|
+
return {
|
180
|
+
type: SET_NODES_UPTIME_FILTER,
|
181
|
+
data: value,
|
182
|
+
} as const;
|
183
|
+
}
|
184
|
+
|
185
|
+
export const setDataWasNotLoaded = () => {
|
186
|
+
return {
|
187
|
+
type: SET_DATA_WAS_NOT_LOADED,
|
188
|
+
} as const;
|
189
|
+
};
|
190
|
+
|
191
|
+
export default storage;
|
@@ -1,12 +1,92 @@
|
|
1
|
+
import type {IResponseError} from '../../../types/api/error';
|
2
|
+
import type {TNodesInfo, TSystemStateInfo} from '../../../types/api/nodes';
|
3
|
+
import type {TPDiskStateInfo} from '../../../types/api/pdisk';
|
4
|
+
import type {
|
5
|
+
TBSGroupStateInfo,
|
6
|
+
THiveStorageGroupStats,
|
7
|
+
TStorageInfo,
|
8
|
+
} from '../../../types/api/storage';
|
1
9
|
import type {ValueOf} from '../../../types/common';
|
10
|
+
import type {NodesUptimeFilterValues} from '../../../utils/nodes';
|
11
|
+
import type {ApiRequestAction} from '../../utils';
|
2
12
|
|
3
13
|
import {STORAGE_TYPES, VISIBLE_ENTITIES} from './constants';
|
14
|
+
import {
|
15
|
+
FETCH_STORAGE,
|
16
|
+
setDataWasNotLoaded,
|
17
|
+
setInitialState,
|
18
|
+
setNodesUptimeFilter,
|
19
|
+
setStorageTextFilter,
|
20
|
+
setStorageType,
|
21
|
+
setUsageFilter,
|
22
|
+
setVisibleEntities,
|
23
|
+
} from './storage';
|
4
24
|
|
5
25
|
export type VisibleEntities = ValueOf<typeof VISIBLE_ENTITIES>;
|
6
26
|
export type StorageType = ValueOf<typeof STORAGE_TYPES>;
|
7
27
|
|
28
|
+
export interface PreparedStorageNode extends TSystemStateInfo {
|
29
|
+
NodeId: number;
|
30
|
+
PDisks: TPDiskStateInfo[] | undefined;
|
31
|
+
|
32
|
+
Missing: number;
|
33
|
+
Uptime: string;
|
34
|
+
}
|
35
|
+
|
36
|
+
export type RawStorageGroup = TBSGroupStateInfo & THiveStorageGroupStats;
|
37
|
+
|
38
|
+
export interface PreparedStorageGroup extends RawStorageGroup {
|
39
|
+
PoolName: string | undefined;
|
40
|
+
|
41
|
+
Read: number;
|
42
|
+
Write: number;
|
43
|
+
Used: number;
|
44
|
+
Limit: number;
|
45
|
+
Missing: number;
|
46
|
+
UsedSpaceFlag: number;
|
47
|
+
Type: string | null;
|
48
|
+
}
|
49
|
+
|
50
|
+
export interface UsageFilter {
|
51
|
+
threshold: number;
|
52
|
+
count: number;
|
53
|
+
}
|
54
|
+
|
8
55
|
export interface StorageApiRequestParams {
|
9
56
|
tenant?: string;
|
10
57
|
nodeId?: string;
|
11
58
|
visibleEntities?: VisibleEntities;
|
12
59
|
}
|
60
|
+
|
61
|
+
export interface StorageState {
|
62
|
+
loading: boolean;
|
63
|
+
wasLoaded: boolean;
|
64
|
+
filter: string;
|
65
|
+
usageFilter: string[];
|
66
|
+
visible: VisibleEntities;
|
67
|
+
nodesUptimeFilter: NodesUptimeFilterValues;
|
68
|
+
type: StorageType;
|
69
|
+
nodes?: TNodesInfo;
|
70
|
+
groups?: TStorageInfo;
|
71
|
+
error?: IResponseError;
|
72
|
+
}
|
73
|
+
|
74
|
+
type GetStorageInfoApiRequestAction = ApiRequestAction<
|
75
|
+
typeof FETCH_STORAGE,
|
76
|
+
{nodes?: TNodesInfo; groups?: TStorageInfo},
|
77
|
+
IResponseError
|
78
|
+
>;
|
79
|
+
|
80
|
+
export type StorageAction =
|
81
|
+
| GetStorageInfoApiRequestAction
|
82
|
+
| ReturnType<typeof setInitialState>
|
83
|
+
| ReturnType<typeof setStorageType>
|
84
|
+
| ReturnType<typeof setStorageTextFilter>
|
85
|
+
| ReturnType<typeof setUsageFilter>
|
86
|
+
| ReturnType<typeof setVisibleEntities>
|
87
|
+
| ReturnType<typeof setNodesUptimeFilter>
|
88
|
+
| ReturnType<typeof setDataWasNotLoaded>;
|
89
|
+
|
90
|
+
export interface StorageStateSlice {
|
91
|
+
storage: StorageState;
|
92
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import {Selector, createSelector} from 'reselect';
|
2
|
+
import {escapeRegExp} from 'lodash';
|
3
|
+
|
4
|
+
import {EFlag} from '../../../types/api/enums';
|
5
|
+
|
6
|
+
import type {RootState} from '..';
|
7
|
+
import type {ProblemFilterValue} from '../settings/types';
|
8
|
+
import {ProblemFilterValues, selectProblemFilter} from '../settings/settings';
|
9
|
+
|
10
|
+
import type {PreparedTenant, TenantsStateSlice} from './types';
|
11
|
+
|
12
|
+
// ==== Filters ====
|
13
|
+
|
14
|
+
const filterTenantsByProblems = (tenants: PreparedTenant[], problemFilter: ProblemFilterValue) => {
|
15
|
+
if (problemFilter === ProblemFilterValues.ALL) {
|
16
|
+
return tenants;
|
17
|
+
}
|
18
|
+
|
19
|
+
return tenants.filter((tenant) => {
|
20
|
+
return tenant.Overall && tenant.Overall !== EFlag.Green;
|
21
|
+
});
|
22
|
+
};
|
23
|
+
|
24
|
+
const filteredTenantsBySearch = (tenants: PreparedTenant[], searchQuery: string) => {
|
25
|
+
return tenants.filter((item) => {
|
26
|
+
const re = new RegExp(escapeRegExp(searchQuery), 'i');
|
27
|
+
return re.test(item.Name) || re.test(item.controlPlaneName);
|
28
|
+
});
|
29
|
+
};
|
30
|
+
|
31
|
+
// ==== Simple selectors ====
|
32
|
+
|
33
|
+
export const selectTenants = (state: TenantsStateSlice) => state.tenants.tenants;
|
34
|
+
export const selectTenantsSearchValue = (state: TenantsStateSlice) => state.tenants.searchValue;
|
35
|
+
|
36
|
+
// ==== Complex selectors ====
|
37
|
+
|
38
|
+
export const selectFilteredTenants: Selector<RootState, PreparedTenant[]> = createSelector(
|
39
|
+
[selectTenants, selectProblemFilter, selectTenantsSearchValue],
|
40
|
+
(tenants, problemFilter, searchQuery) => {
|
41
|
+
let result = filterTenantsByProblems(tenants, problemFilter);
|
42
|
+
result = filteredTenantsBySearch(result, searchQuery);
|
43
|
+
|
44
|
+
return result;
|
45
|
+
},
|
46
|
+
);
|
@@ -4,10 +4,13 @@ import '../../../services/api';
|
|
4
4
|
import {createRequestActionTypes, createApiRequest} from '../../utils';
|
5
5
|
|
6
6
|
import type {TenantsAction, TenantsState} from './types';
|
7
|
+
import {prepareTenants} from './utils';
|
7
8
|
|
8
9
|
export const FETCH_TENANTS = createRequestActionTypes('tenants', 'FETCH_TENANTS');
|
9
10
|
|
10
|
-
const
|
11
|
+
const SET_SEARCH_VALUE = 'tenants/SET_SEARCH_VALUE';
|
12
|
+
|
13
|
+
const initialState = {loading: true, wasLoaded: false, searchValue: '', tenants: []};
|
11
14
|
|
12
15
|
const tenants: Reducer<TenantsState, TenantsAction> = (state = initialState, action) => {
|
13
16
|
switch (action.type) {
|
@@ -33,6 +36,12 @@ const tenants: Reducer<TenantsState, TenantsAction> = (state = initialState, act
|
|
33
36
|
loading: false,
|
34
37
|
};
|
35
38
|
}
|
39
|
+
case SET_SEARCH_VALUE: {
|
40
|
+
return {
|
41
|
+
...state,
|
42
|
+
searchValue: action.data,
|
43
|
+
};
|
44
|
+
}
|
36
45
|
default:
|
37
46
|
return state;
|
38
47
|
}
|
@@ -45,22 +54,20 @@ export function getTenantsInfo(clusterName?: string) {
|
|
45
54
|
dataHandler: (response, getState) => {
|
46
55
|
const {singleClusterMode} = getState();
|
47
56
|
|
48
|
-
if (
|
49
|
-
return
|
50
|
-
} else {
|
51
|
-
return response.TenantInfo?.map((tenant) => {
|
52
|
-
const node = tenant.Nodes ? tenant.Nodes[0] : {};
|
53
|
-
const address =
|
54
|
-
node.Host && node.Endpoints
|
55
|
-
? node.Endpoints.find((endpoint) => endpoint.Name === 'http-mon')
|
56
|
-
?.Address
|
57
|
-
: undefined;
|
58
|
-
const backend = node.Host ? `${node.Host}${address ? address : ''}` : undefined;
|
59
|
-
return {...tenant, backend};
|
60
|
-
});
|
57
|
+
if (!response.TenantInfo) {
|
58
|
+
return [];
|
61
59
|
}
|
60
|
+
|
61
|
+
return prepareTenants(response.TenantInfo, singleClusterMode);
|
62
62
|
},
|
63
63
|
});
|
64
64
|
}
|
65
65
|
|
66
|
+
export const setSearchValue = (value: string) => {
|
67
|
+
return {
|
68
|
+
type: SET_SEARCH_VALUE,
|
69
|
+
data: value,
|
70
|
+
} as const;
|
71
|
+
};
|
72
|
+
|
66
73
|
export default tenants;
|
@@ -1,17 +1,32 @@
|
|
1
|
-
import {FETCH_TENANTS} from './tenants';
|
1
|
+
import {FETCH_TENANTS, setSearchValue} from './tenants';
|
2
2
|
|
3
3
|
import type {TTenant} from '../../../types/api/tenant';
|
4
|
+
import type {IResponseError} from '../../../types/api/error';
|
4
5
|
import type {ApiRequestAction} from '../../utils';
|
5
6
|
|
6
7
|
export interface PreparedTenant extends TTenant {
|
7
|
-
backend
|
8
|
+
backend: string | undefined;
|
9
|
+
sharedTenantName: string | undefined;
|
10
|
+
controlPlaneName: string;
|
11
|
+
cpu: number;
|
12
|
+
memory: number;
|
13
|
+
storage: number;
|
14
|
+
nodesCount: number;
|
15
|
+
groupsCount: number;
|
8
16
|
}
|
9
17
|
|
10
18
|
export interface TenantsState {
|
11
19
|
loading: boolean;
|
12
20
|
wasLoaded: boolean;
|
13
|
-
|
14
|
-
|
21
|
+
searchValue: string;
|
22
|
+
tenants: PreparedTenant[];
|
23
|
+
error?: IResponseError;
|
15
24
|
}
|
16
25
|
|
17
|
-
export type TenantsAction =
|
26
|
+
export type TenantsAction =
|
27
|
+
| ApiRequestAction<typeof FETCH_TENANTS, PreparedTenant[], IResponseError>
|
28
|
+
| ReturnType<typeof setSearchValue>;
|
29
|
+
|
30
|
+
export interface TenantsStateSlice {
|
31
|
+
tenants: TenantsState;
|
32
|
+
}
|