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.
Files changed (64) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +7 -4
  3. package/dist/components/EntityStatus/EntityStatus.js +3 -1
  4. package/dist/components/FormattedBytes/FormattedBytes.tsx +10 -0
  5. package/dist/components/FormattedBytes/utils.tsx +13 -0
  6. package/dist/components/FullNodeViewer/FullNodeViewer.tsx +73 -0
  7. package/dist/components/InfoViewer/formatters/table.ts +6 -5
  8. package/dist/components/ProblemFilter/ProblemFilter.tsx +2 -2
  9. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +4 -4
  10. package/dist/components/TruncatedQuery/{TruncatedQuery.js → TruncatedQuery.tsx} +10 -8
  11. package/dist/containers/AsideNavigation/AsideNavigation.tsx +6 -6
  12. package/dist/containers/Cluster/Cluster.tsx +10 -6
  13. package/dist/containers/Node/Node.tsx +3 -3
  14. package/dist/containers/Nodes/Nodes.tsx +2 -2
  15. package/dist/containers/Storage/PDisk/PDisk.tsx +2 -7
  16. package/dist/containers/Storage/Storage.tsx +240 -0
  17. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +45 -40
  18. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +12 -16
  19. package/dist/containers/Storage/UsageFilter/UsageFilter.scss +1 -0
  20. package/dist/containers/Storage/UsageFilter/UsageFilter.tsx +17 -17
  21. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -3
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +7 -4
  23. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +0 -15
  24. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +10 -3
  25. package/dist/containers/Tenant/{Preview → Query/Preview}/Preview.scss +1 -1
  26. package/dist/containers/Tenant/Query/Preview/Preview.tsx +121 -0
  27. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +1 -1
  28. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +6 -8
  29. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +1 -1
  30. package/dist/containers/Tenant/Query/i18n/en.json +8 -1
  31. package/dist/containers/Tenant/Query/i18n/ru.json +8 -1
  32. package/dist/containers/Tenants/Tenants.tsx +269 -0
  33. package/dist/services/api.ts +8 -3
  34. package/dist/store/reducers/nodes/nodes.ts +4 -4
  35. package/dist/store/reducers/partitions/types.ts +3 -3
  36. package/dist/store/reducers/settings/settings.ts +4 -2
  37. package/dist/store/reducers/settings/types.ts +3 -1
  38. package/dist/store/reducers/storage/selectors.ts +279 -0
  39. package/dist/store/reducers/storage/storage.ts +191 -0
  40. package/dist/store/reducers/storage/types.ts +80 -0
  41. package/dist/store/reducers/tenants/selectors.ts +46 -0
  42. package/dist/store/reducers/tenants/tenants.ts +21 -14
  43. package/dist/store/reducers/tenants/types.ts +20 -5
  44. package/dist/store/reducers/tenants/utils.ts +68 -0
  45. package/dist/types/additionalProps.ts +8 -0
  46. package/dist/types/api/storage.ts +1 -1
  47. package/dist/types/store/topic.ts +3 -3
  48. package/dist/utils/bytesParsers/__test__/formatBytes.test.ts +38 -0
  49. package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +2 -2
  50. package/dist/utils/bytesParsers/formatBytes.ts +132 -0
  51. package/dist/utils/bytesParsers/i18n/en.json +1 -0
  52. package/dist/utils/bytesParsers/i18n/ru.json +1 -0
  53. package/dist/utils/bytesParsers/index.ts +1 -1
  54. package/dist/utils/index.js +5 -10
  55. package/dist/utils/numeral.ts +8 -0
  56. package/package.json +1 -1
  57. package/dist/components/FullNodeViewer/FullNodeViewer.js +0 -89
  58. package/dist/containers/Node/NodeOverview/NodeOverview.scss +0 -0
  59. package/dist/containers/Node/NodeOverview/NodeOverview.tsx +0 -21
  60. package/dist/containers/Storage/Storage.js +0 -350
  61. package/dist/containers/Tenant/Preview/Preview.js +0 -168
  62. package/dist/containers/Tenants/Tenants.js +0 -363
  63. package/dist/store/reducers/storage/storage.js +0 -404
  64. 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 initialState = {loading: true, wasLoaded: false};
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 (singleClusterMode) {
49
- return response.TenantInfo;
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?: string;
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
- tenants?: PreparedTenant[];
14
- error?: unknown;
21
+ searchValue: string;
22
+ tenants: PreparedTenant[];
23
+ error?: IResponseError;
15
24
  }
16
25
 
17
- export type TenantsAction = ApiRequestAction<typeof FETCH_TENANTS, PreparedTenant[], unknown>;
26
+ export type TenantsAction =
27
+ | ApiRequestAction<typeof FETCH_TENANTS, PreparedTenant[], IResponseError>
28
+ | ReturnType<typeof setSearchValue>;
29
+
30
+ export interface TenantsStateSlice {
31
+ tenants: TenantsState;
32
+ }