ydb-embedded-ui 4.19.2 → 4.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/components/LinkToSchemaObject/LinkToSchemaObject.tsx +20 -0
  3. package/dist/components/ProgressViewer/ProgressViewer.tsx +2 -1
  4. package/dist/components/QueryExecutionStatus/QueryExecutionStatus.tsx +3 -2
  5. package/dist/components/UsageLabel/UsageLabel.scss +6 -0
  6. package/dist/components/UsageLabel/UsageLabel.tsx +22 -0
  7. package/dist/containers/AsideNavigation/AsideNavigation.tsx +13 -8
  8. package/dist/containers/AsideNavigation/i18n/en.json +13 -0
  9. package/dist/containers/AsideNavigation/i18n/index.ts +11 -0
  10. package/dist/containers/AsideNavigation/i18n/ru.json +13 -0
  11. package/dist/containers/Node/NodeStructure/Pdisk.tsx +74 -68
  12. package/dist/containers/Node/NodeStructure/Vdisk.tsx +9 -33
  13. package/dist/containers/Nodes/Nodes.tsx +10 -2
  14. package/dist/containers/Nodes/getNodesColumns.tsx +206 -122
  15. package/dist/containers/Storage/Storage.tsx +9 -2
  16. package/dist/containers/Storage/utils/index.ts +1 -22
  17. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +2 -3
  18. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +0 -1
  19. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +4 -2
  20. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +1 -0
  21. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx +8 -1
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +11 -1
  23. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/IssuesViewer/IssueTree.tsx +0 -1
  24. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +3 -0
  25. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TenantCpu.tsx +21 -0
  26. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +53 -0
  27. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +53 -0
  28. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +85 -0
  29. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +53 -0
  30. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TenantMemory.tsx +9 -0
  31. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +55 -0
  32. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +40 -0
  33. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +35 -19
  34. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +53 -0
  35. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +15 -7
  36. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +3 -3
  37. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +3 -3
  38. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +0 -2
  39. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +13 -61
  40. package/dist/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +82 -0
  41. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx +2 -2
  42. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +18 -97
  43. package/dist/containers/Tenant/Diagnostics/TopShards/getTopShardsColumns.tsx +138 -0
  44. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +2 -4
  45. package/dist/containers/Tenant/Query/ExecuteResult/{ExecuteResult.js → ExecuteResult.tsx} +51 -31
  46. package/dist/containers/Tenant/Query/Issues/Issues.tsx +4 -6
  47. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +1 -1
  48. package/dist/containers/Tenant/utils/paneVisibilityToggleHelpers.tsx +1 -1
  49. package/dist/routes.ts +6 -0
  50. package/dist/store/reducers/{executeTopQueries.ts → executeTopQueries/executeTopQueries.ts} +23 -75
  51. package/dist/{types/store/executeTopQueries.ts → store/reducers/executeTopQueries/types.ts} +3 -7
  52. package/dist/store/reducers/executeTopQueries/utils.ts +36 -0
  53. package/dist/store/reducers/index.ts +12 -2
  54. package/dist/store/reducers/nodes/types.ts +1 -0
  55. package/dist/store/reducers/nodes/utils.ts +16 -6
  56. package/dist/store/reducers/{shardsWorkload.ts → shardsWorkload/shardsWorkload.ts} +5 -11
  57. package/dist/{types/store/shardsWorkload.ts → store/reducers/shardsWorkload/types.ts} +3 -7
  58. package/dist/store/reducers/storage/storage.ts +1 -1
  59. package/dist/store/reducers/tenantOverview/topNodesByCpu/topNodesByCpu.ts +87 -0
  60. package/dist/store/reducers/tenantOverview/topNodesByCpu/types.ts +29 -0
  61. package/dist/store/reducers/tenantOverview/topNodesByLoad/topNodesByLoad.ts +87 -0
  62. package/dist/store/reducers/tenantOverview/topNodesByLoad/types.ts +29 -0
  63. package/dist/store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory.ts +87 -0
  64. package/dist/store/reducers/tenantOverview/topNodesByMemory/types.ts +29 -0
  65. package/dist/store/reducers/tenantOverview/topQueries/tenantOverviewTopQueries.ts +93 -0
  66. package/dist/store/reducers/tenantOverview/topQueries/types.ts +14 -0
  67. package/dist/store/reducers/tenantOverview/topShards/tenantOverviewTopShards.ts +103 -0
  68. package/dist/store/reducers/tenantOverview/topShards/types.ts +14 -0
  69. package/dist/store/reducers/tenantOverview/topShards/utils.ts +3 -0
  70. package/dist/styles/mixins.scss +4 -0
  71. package/dist/types/additionalProps.ts +3 -1
  72. package/dist/types/api/compute.ts +1 -1
  73. package/dist/types/react-json-inspector.d.ts +21 -0
  74. package/dist/utils/diagnostics.ts +11 -0
  75. package/dist/utils/generateEvaluator.ts +21 -0
  76. package/package.json +1 -1
@@ -1,17 +1,18 @@
1
- import DataTable, {Column} from '@gravity-ui/react-data-table';
1
+ import DataTable, {type Column} from '@gravity-ui/react-data-table';
2
2
  import {Popover} from '@gravity-ui/uikit';
3
3
 
4
4
  import {PoolsGraph} from '../../components/PoolsGraph/PoolsGraph';
5
5
  import {ProgressViewer} from '../../components/ProgressViewer/ProgressViewer';
6
6
  import {TabletsStatistic} from '../../components/TabletsStatistic';
7
7
  import {NodeHostWrapper} from '../../components/NodeHostWrapper/NodeHostWrapper';
8
-
9
- import {isSortableNodesProperty} from '../../utils/nodes';
10
- import {formatBytesToGigabyte} from '../../utils/dataFormatters/dataFormatters';
11
-
8
+ import {
9
+ formatBytesToGigabyte,
10
+ formatStorageValuesToGb,
11
+ } from '../../utils/dataFormatters/dataFormatters';
12
12
  import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
13
-
14
- import type {NodeAddress} from '../../types/additionalProps';
13
+ import type {GetNodeRefFunc} from '../../types/additionalProps';
14
+ import {getLoadSeverityForNode} from '../../store/reducers/nodes/utils';
15
+ import {UsageLabel} from '../../components/UsageLabel/UsageLabel';
15
16
 
16
17
  const NODES_COLUMNS_IDS = {
17
18
  NodeId: 'NodeId',
@@ -24,131 +25,214 @@ const NODES_COLUMNS_IDS = {
24
25
  CPU: 'CPU',
25
26
  LoadAverage: 'LoadAverage',
26
27
  Tablets: 'Tablets',
28
+ TopNodesLoadAverage: 'TopNodesLoadAverage',
29
+ TopNodesMemory: 'TopNodesMemory',
27
30
  };
28
31
 
29
32
  interface GetNodesColumnsProps {
30
33
  tabletsPath?: string;
31
- getNodeRef?: (node?: NodeAddress) => string | null;
34
+ getNodeRef?: GetNodeRefFunc;
32
35
  }
33
36
 
37
+ const nodeIdColumn: Column<NodesPreparedEntity> = {
38
+ name: NODES_COLUMNS_IDS.NodeId,
39
+ header: '#',
40
+ width: '80px',
41
+ align: DataTable.RIGHT,
42
+ sortable: false,
43
+ };
44
+
45
+ const getHostColumn = (
46
+ getNodeRef?: GetNodeRefFunc,
47
+ fixedWidth = false,
48
+ ): Column<NodesPreparedEntity> => ({
49
+ name: NODES_COLUMNS_IDS.Host,
50
+ render: ({row}) => {
51
+ return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
52
+ },
53
+ width: fixedWidth ? '350px' : undefined,
54
+ align: DataTable.LEFT,
55
+ sortable: false,
56
+ });
57
+
58
+ const dataCenterColumn: Column<NodesPreparedEntity> = {
59
+ name: NODES_COLUMNS_IDS.DC,
60
+ header: 'DC',
61
+ align: DataTable.LEFT,
62
+ render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
63
+ width: '60px',
64
+ };
65
+
66
+ const rackColumn: Column<NodesPreparedEntity> = {
67
+ name: NODES_COLUMNS_IDS.Rack,
68
+ header: 'Rack',
69
+ align: DataTable.LEFT,
70
+ render: ({row}) => (row.Rack ? row.Rack : '—'),
71
+ width: '80px',
72
+ };
73
+
74
+ const versionColumn: Column<NodesPreparedEntity> = {
75
+ name: NODES_COLUMNS_IDS.Version,
76
+ width: '200px',
77
+ align: DataTable.LEFT,
78
+ render: ({row}) => {
79
+ return <Popover content={row.Version}>{row.Version}</Popover>;
80
+ },
81
+ sortable: false,
82
+ };
83
+
84
+ const uptimeColumn: Column<NodesPreparedEntity> = {
85
+ name: NODES_COLUMNS_IDS.Uptime,
86
+ header: 'Uptime',
87
+ sortAccessor: ({StartTime}) => StartTime && -StartTime,
88
+ align: DataTable.RIGHT,
89
+ width: '110px',
90
+ sortable: false,
91
+ };
92
+
93
+ const memoryColumn: Column<NodesPreparedEntity> = {
94
+ name: NODES_COLUMNS_IDS.Memory,
95
+ header: 'Memory',
96
+ sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
97
+ defaultOrder: DataTable.DESCENDING,
98
+ render: ({row}) => {
99
+ if (row.MemoryUsed) {
100
+ return formatBytesToGigabyte(row.MemoryUsed);
101
+ } else {
102
+ return '—';
103
+ }
104
+ },
105
+ align: DataTable.RIGHT,
106
+ width: '120px',
107
+ };
108
+
109
+ const cpuColumn: Column<NodesPreparedEntity> = {
110
+ name: NODES_COLUMNS_IDS.CPU,
111
+ header: 'CPU',
112
+ sortAccessor: ({PoolStats = []}) => Math.max(...PoolStats.map(({Usage}) => Number(Usage))),
113
+ defaultOrder: DataTable.DESCENDING,
114
+ render: ({row}) => (row.PoolStats ? <PoolsGraph pools={row.PoolStats} /> : '—'),
115
+ align: DataTable.LEFT,
116
+ width: '80px',
117
+ sortable: false,
118
+ };
119
+
120
+ const loadAverageColumn: Column<NodesPreparedEntity> = {
121
+ name: NODES_COLUMNS_IDS.LoadAverage,
122
+ header: 'Load average',
123
+ sortAccessor: ({LoadAverage = []}) =>
124
+ LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
125
+ defaultOrder: DataTable.DESCENDING,
126
+ render: ({row}) =>
127
+ row.LoadAverage && row.LoadAverage.length > 0 ? (
128
+ <ProgressViewer
129
+ value={row.LoadAverage[0]}
130
+ percents={true}
131
+ colorizeProgress={true}
132
+ capacity={100}
133
+ />
134
+ ) : (
135
+ '—'
136
+ ),
137
+ align: DataTable.LEFT,
138
+ width: '140px',
139
+ sortable: false,
140
+ };
141
+
142
+ const getTabletsColumn = (tabletsPath?: string): Column<NodesPreparedEntity> => ({
143
+ name: NODES_COLUMNS_IDS.Tablets,
144
+ width: '430px',
145
+ render: ({row}) => {
146
+ return row.Tablets ? (
147
+ <TabletsStatistic
148
+ path={tabletsPath ?? row.TenantName}
149
+ nodeIds={[row.NodeId]}
150
+ tablets={row.Tablets}
151
+ />
152
+ ) : (
153
+ '—'
154
+ );
155
+ },
156
+ align: DataTable.LEFT,
157
+ sortable: false,
158
+ });
159
+
160
+ const topNodesLoadAverageColumn: Column<NodesPreparedEntity> = {
161
+ name: NODES_COLUMNS_IDS.TopNodesLoadAverage,
162
+ header: 'Load',
163
+ render: ({row}) =>
164
+ row.LoadAverage && row.LoadAverage.length > 0 ? (
165
+ <UsageLabel
166
+ value={row.LoadAverage[0].toFixed()}
167
+ theme={getLoadSeverityForNode(row.LoadAverage[0])}
168
+ />
169
+ ) : (
170
+ '—'
171
+ ),
172
+ align: DataTable.LEFT,
173
+ width: '80px',
174
+ sortable: false,
175
+ };
176
+
177
+ const topNodesMemoryColumn: Column<NodesPreparedEntity> = {
178
+ name: NODES_COLUMNS_IDS.TopNodesMemory,
179
+ header: 'Memory',
180
+ render: ({row}) =>
181
+ row.MemoryUsed ? (
182
+ <ProgressViewer
183
+ value={row.MemoryUsed}
184
+ capacity={row.MemoryLimit}
185
+ formatValues={formatStorageValuesToGb}
186
+ colorizeProgress={true}
187
+ />
188
+ ) : (
189
+ '—'
190
+ ),
191
+ align: DataTable.LEFT,
192
+ width: '140px',
193
+ sortable: false,
194
+ };
195
+
34
196
  export function getNodesColumns({
35
197
  tabletsPath,
36
198
  getNodeRef,
37
199
  }: GetNodesColumnsProps): Column<NodesPreparedEntity>[] {
38
- const columns: Column<NodesPreparedEntity>[] = [
39
- {
40
- name: NODES_COLUMNS_IDS.NodeId,
41
- header: '#',
42
- width: '80px',
43
- align: DataTable.RIGHT,
44
- },
45
- {
46
- name: NODES_COLUMNS_IDS.Host,
47
- render: ({row}) => {
48
- return <NodeHostWrapper node={row} getNodeRef={getNodeRef} />;
49
- },
50
- width: '350px',
51
- align: DataTable.LEFT,
52
- },
53
- {
54
- name: NODES_COLUMNS_IDS.DC,
55
- header: 'DC',
56
- align: DataTable.LEFT,
57
- render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
58
- width: '60px',
59
- },
60
- {
61
- name: NODES_COLUMNS_IDS.Rack,
62
- header: 'Rack',
63
- align: DataTable.LEFT,
64
- render: ({row}) => (row.Rack ? row.Rack : '—'),
65
- width: '80px',
66
- },
67
- {
68
- name: NODES_COLUMNS_IDS.Version,
69
- width: '200px',
70
- align: DataTable.LEFT,
71
- render: ({row}) => {
72
- return <Popover content={row.Version}>{row.Version}</Popover>;
73
- },
74
- },
75
- {
76
- name: NODES_COLUMNS_IDS.Uptime,
77
- header: 'Uptime',
78
- sortAccessor: ({StartTime}) => StartTime && -StartTime,
79
- align: DataTable.RIGHT,
80
- width: '110px',
81
- },
82
- {
83
- name: NODES_COLUMNS_IDS.Memory,
84
- header: 'Memory',
85
- sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
86
- defaultOrder: DataTable.DESCENDING,
87
- render: ({row}) => {
88
- if (row.MemoryUsed) {
89
- return formatBytesToGigabyte(row.MemoryUsed);
90
- } else {
91
- return '—';
92
- }
93
- },
94
- align: DataTable.RIGHT,
95
- width: '120px',
96
- },
97
- {
98
- name: NODES_COLUMNS_IDS.CPU,
99
- header: 'CPU',
100
- sortAccessor: ({PoolStats = []}) =>
101
- PoolStats.reduce((acc, item) => {
102
- if (item.Usage) {
103
- return acc + item.Usage;
104
- } else {
105
- return acc;
106
- }
107
- }, 0),
108
- defaultOrder: DataTable.DESCENDING,
109
- render: ({row}) => (row.PoolStats ? <PoolsGraph pools={row.PoolStats} /> : '—'),
110
- align: DataTable.LEFT,
111
- width: '120px',
112
- },
113
- {
114
- name: NODES_COLUMNS_IDS.LoadAverage,
115
- header: 'Load average',
116
- sortAccessor: ({LoadAverage = []}) =>
117
- LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
118
- defaultOrder: DataTable.DESCENDING,
119
- render: ({row}) =>
120
- row.LoadAverage && row.LoadAverage.length > 0 ? (
121
- <ProgressViewer
122
- value={row.LoadAverage[0]}
123
- capacity={100}
124
- percents={true}
125
- colorizeProgress={true}
126
- />
127
- ) : (
128
- '—'
129
- ),
130
- align: DataTable.LEFT,
131
- width: '140px',
132
- },
133
- {
134
- name: NODES_COLUMNS_IDS.Tablets,
135
- width: '430px',
136
- render: ({row}) => {
137
- return row.Tablets ? (
138
- <TabletsStatistic
139
- path={tabletsPath ?? row.TenantName}
140
- nodeIds={[row.NodeId]}
141
- tablets={row.Tablets}
142
- />
143
- ) : (
144
- '—'
145
- );
146
- },
147
- align: DataTable.LEFT,
148
- },
200
+ return [
201
+ nodeIdColumn,
202
+ getHostColumn(getNodeRef, true),
203
+ dataCenterColumn,
204
+ rackColumn,
205
+ versionColumn,
206
+ uptimeColumn,
207
+ memoryColumn,
208
+ cpuColumn,
209
+ loadAverageColumn,
210
+ getTabletsColumn(tabletsPath),
149
211
  ];
212
+ }
150
213
 
151
- return columns.map((column) => {
152
- return {...column, sortable: isSortableNodesProperty(column.name)};
153
- });
214
+ export function getTopNodesByLoadColumns(
215
+ getNodeRef?: GetNodeRefFunc,
216
+ ): Column<NodesPreparedEntity>[] {
217
+ return [topNodesLoadAverageColumn, nodeIdColumn, getHostColumn(getNodeRef), versionColumn];
218
+ }
219
+
220
+ export function getTopNodesByCpuColumns(
221
+ getNodeRef?: GetNodeRefFunc,
222
+ ): Column<NodesPreparedEntity>[] {
223
+ return [cpuColumn, nodeIdColumn, getHostColumn(getNodeRef)];
224
+ }
225
+
226
+ export function getTopNodesByMemoryColumns({
227
+ tabletsPath,
228
+ getNodeRef,
229
+ }: GetNodesColumnsProps): Column<NodesPreparedEntity>[] {
230
+ return [
231
+ nodeIdColumn,
232
+ getHostColumn(getNodeRef, true),
233
+ uptimeColumn,
234
+ topNodesMemoryColumn,
235
+ topNodesLoadAverageColumn,
236
+ getTabletsColumn(tabletsPath),
237
+ ];
154
238
  }
@@ -73,7 +73,7 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
73
73
  loading,
74
74
  wasLoaded,
75
75
  error,
76
- type: storageType,
76
+ type,
77
77
  visible: visibleEntities,
78
78
  filter,
79
79
  usageFilter,
@@ -87,6 +87,10 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
87
87
  const nodesSortParams = useTypedSelector(selectNodesSortParams);
88
88
  const groupsSortParams = useTypedSelector(selectGroupsSortParams);
89
89
 
90
+ // Do not display Nodes table for Node page (NodeId present)
91
+ const isNodePage = nodeId !== undefined;
92
+ const storageType = isNodePage ? STORAGE_TYPES.groups : type;
93
+
90
94
  useEffect(() => {
91
95
  dispatch(getNodesList());
92
96
 
@@ -228,7 +232,10 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
228
232
  />
229
233
  </div>
230
234
 
231
- <StorageTypeFilter value={storageType} onChange={handleStorageTypeChange} />
235
+ {!isNodePage && (
236
+ <StorageTypeFilter value={storageType} onChange={handleStorageTypeChange} />
237
+ )}
238
+
232
239
  <StorageVisibleEntitiesFilter
233
240
  value={visibleEntities}
234
241
  onChange={handleGroupVisibilityChange}
@@ -1,30 +1,9 @@
1
1
  import type {PreparedStorageGroup} from '../../../store/reducers/storage/types';
2
2
  import {EFlag} from '../../../types/api/enums';
3
+ import {generateEvaluator} from '../../../utils/generateEvaluator';
3
4
 
4
5
  export * from './constants';
5
6
 
6
- const generateEvaluator =
7
- <OkLevel extends string, WarnLevel extends string, CritLevel extends string>(
8
- warn: number,
9
- crit: number,
10
- levels: [OkLevel, WarnLevel, CritLevel],
11
- ) =>
12
- (value: number) => {
13
- if (0 <= value && value < warn) {
14
- return levels[0];
15
- }
16
-
17
- if (warn <= value && value < crit) {
18
- return levels[1];
19
- }
20
-
21
- if (crit <= value) {
22
- return levels[2];
23
- }
24
-
25
- return undefined;
26
- };
27
-
28
7
  const defaultDegradationEvaluator = generateEvaluator(1, 2, ['success', 'warning', 'danger']);
29
8
 
30
9
  const degradationEvaluators = {
@@ -1,7 +1,6 @@
1
1
  import {useCallback, useEffect, useState} from 'react';
2
2
  import {shallowEqual, useDispatch} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
- // @ts-ignore
5
4
  import JSONTree from 'react-json-inspector';
6
5
  import 'react-json-inspector/json-inspector.css';
7
6
 
@@ -99,14 +98,14 @@ const Describe = ({tenant, type}: IDescribeProps) => {
99
98
  <JSONTree
100
99
  data={preparedDescribeData}
101
100
  className={b('tree')}
102
- onClick={({path}: {path: string}) => {
101
+ onClick={({path}) => {
103
102
  const newValue = !(expandMap.get(path) || false);
104
103
  expandMap.set(path, newValue);
105
104
  }}
106
105
  searchOptions={{
107
106
  debounceTime: 300,
108
107
  }}
109
- isExpanded={(keypath: string) => {
108
+ isExpanded={(keypath) => {
110
109
  return expandMap.get(keypath) || false;
111
110
  }}
112
111
  />
@@ -11,7 +11,6 @@
11
11
  flex-direction: column;
12
12
 
13
13
  min-width: 300px;
14
- height: max-content;
15
14
  }
16
15
 
17
16
  &__modal {
@@ -5,7 +5,7 @@ import cn from 'bem-cn-lite';
5
5
  import {Button, Modal} from '@gravity-ui/uikit';
6
6
 
7
7
  import type {EPathType} from '../../../../types/api/schema';
8
- import type {AdditionalTenantsProps} from '../../../../types/additionalProps';
8
+ import type {AdditionalNodesProps, AdditionalTenantsProps} from '../../../../types/additionalProps';
9
9
  import {Icon} from '../../../../components/Icon';
10
10
  import {useSetting} from '../../../../utils/hooks';
11
11
  import {DISPLAY_METRICS_CARDS_FOR_TENANT_DIAGNOSTICS} from '../../../../utils/constants';
@@ -21,12 +21,13 @@ interface DetailedOverviewProps {
21
21
  className?: string;
22
22
  tenantName: string;
23
23
  additionalTenantProps?: AdditionalTenantsProps;
24
+ additionalNodesProps?: AdditionalNodesProps;
24
25
  }
25
26
 
26
27
  const b = cn('kv-detailed-overview');
27
28
 
28
29
  function DetailedOverview(props: DetailedOverviewProps) {
29
- const {type, tenantName, additionalTenantProps} = props;
30
+ const {type, tenantName, additionalTenantProps, additionalNodesProps} = props;
30
31
 
31
32
  const [isModalVisible, setIsModalVisible] = useState(false);
32
33
 
@@ -65,6 +66,7 @@ function DetailedOverview(props: DetailedOverviewProps) {
65
66
  <TenantOverview
66
67
  tenantName={tenantName}
67
68
  additionalTenantProps={additionalTenantProps}
69
+ additionalNodesProps={additionalNodesProps}
68
70
  />
69
71
  </div>
70
72
  );
@@ -109,6 +109,7 @@ function Diagnostics(props: DiagnosticsProps) {
109
109
  type={type}
110
110
  tenantName={tenantNameString}
111
111
  additionalTenantProps={props.additionalTenantProps}
112
+ additionalNodesProps={props.additionalNodesProps}
112
113
  />
113
114
  );
114
115
  }
@@ -3,6 +3,7 @@ import cn from 'bem-cn-lite';
3
3
  import type {IResponseError} from '../../../../../types/api/error';
4
4
  import type {IIssuesTree} from '../../../../../types/store/healthcheck';
5
5
  import {ResponseError} from '../../../../../components/Errors/ResponseError';
6
+ import {Loader} from '../../../../../components/Loader';
6
7
 
7
8
  import IssueTree from './IssuesViewer/IssueTree';
8
9
 
@@ -13,17 +14,23 @@ const b = cn('healthcheck');
13
14
 
14
15
  interface HealthcheckDetailsProps {
15
16
  issueTrees?: IIssuesTree[];
17
+ loading?: boolean;
18
+ wasLoaded?: boolean;
16
19
  error?: IResponseError;
17
20
  }
18
21
 
19
22
  export function HealthcheckDetails(props: HealthcheckDetailsProps) {
20
- const {issueTrees, error} = props;
23
+ const {issueTrees, loading, wasLoaded, error} = props;
21
24
 
22
25
  const renderContent = () => {
23
26
  if (error) {
24
27
  return <ResponseError error={error} defaultMessage={i18n('no-data')} />;
25
28
  }
26
29
 
30
+ if (loading && !wasLoaded) {
31
+ return <Loader size="m" />;
32
+ }
33
+
27
34
  if (!issueTrees || !issueTrees.length) {
28
35
  return i18n('status_message.ok');
29
36
  }
@@ -9,6 +9,7 @@ import type {IResponseError} from '../../../../../types/api/error';
9
9
  import {DiagnosticCard} from '../../../../../components/DiagnosticCard/DiagnosticCard';
10
10
  import EntityStatus from '../../../../../components/EntityStatus/EntityStatus';
11
11
  import {ResponseError} from '../../../../../components/Errors/ResponseError';
12
+ import {Loader} from '../../../../../components/Loader';
12
13
 
13
14
  import i18n from './i18n';
14
15
  import './Healthcheck.scss';
@@ -19,17 +20,22 @@ interface HealthcheckPreviewProps {
19
20
  selfCheckResult: SelfCheckResult;
20
21
  issuesStatistics?: [StatusFlag, number][];
21
22
  loading?: boolean;
23
+ wasLoaded?: boolean;
22
24
  onUpdate: VoidFunction;
23
25
  error?: IResponseError;
24
26
  active?: boolean;
25
27
  }
26
28
 
27
29
  export function HealthcheckPreview(props: HealthcheckPreviewProps) {
28
- const {selfCheckResult, issuesStatistics, loading, onUpdate, error, active} = props;
30
+ const {selfCheckResult, issuesStatistics, loading, wasLoaded, onUpdate, error, active} = props;
29
31
 
30
32
  const renderHeader = () => {
31
33
  const modifier = selfCheckResult.toLowerCase();
32
34
 
35
+ if (loading && !wasLoaded) {
36
+ return null;
37
+ }
38
+
33
39
  return (
34
40
  <div className={b('preview-header')}>
35
41
  <div className={b('preview-title-wrapper')}>
@@ -50,6 +56,10 @@ export function HealthcheckPreview(props: HealthcheckPreviewProps) {
50
56
  return <ResponseError error={error} defaultMessage={i18n('no-data')} />;
51
57
  }
52
58
 
59
+ if (loading && !wasLoaded) {
60
+ return <Loader size="m" />;
61
+ }
62
+
53
63
  return (
54
64
  <div className={b('preview-content')}>
55
65
  {!issuesStatistics || !issuesStatistics.length ? (
@@ -2,7 +2,6 @@ import {useCallback, useState} from 'react';
2
2
  import cn from 'bem-cn-lite';
3
3
  import _omit from 'lodash/omit';
4
4
 
5
- // @ts-ignore
6
5
  import JSONTree from 'react-json-inspector';
7
6
 
8
7
  import {TreeView} from 'ydb-ui-components';
@@ -38,6 +38,7 @@ interface MetricsCardsProps {
38
38
  selfCheckResult: SelfCheckResult;
39
39
  fetchHealthcheck: VoidFunction;
40
40
  healthcheckLoading?: boolean;
41
+ healthCheckWasLoaded?: boolean;
41
42
  healthcheckError?: IResponseError;
42
43
  }
43
44
 
@@ -47,6 +48,7 @@ export function MetricsCards({
47
48
  selfCheckResult,
48
49
  fetchHealthcheck,
49
50
  healthcheckLoading,
51
+ healthCheckWasLoaded,
50
52
  healthcheckError,
51
53
  }: MetricsCardsProps) {
52
54
  const location = useLocation();
@@ -124,6 +126,7 @@ export function MetricsCards({
124
126
  issuesStatistics={issuesStatistics}
125
127
  onUpdate={fetchHealthcheck}
126
128
  loading={healthcheckLoading}
129
+ wasLoaded={healthCheckWasLoaded}
127
130
  error={healthcheckError}
128
131
  active={metricsTab === TENANT_METRICS_TABS_IDS.healthcheck}
129
132
  />
@@ -0,0 +1,21 @@
1
+ import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
2
+ import {TopNodesByLoad} from './TopNodesByLoad';
3
+ import {TopNodesByCpu} from './TopNodesByCpu';
4
+ import {TopShards} from './TopShards';
5
+ import {TopQueries} from './TopQueries';
6
+
7
+ interface TenantCpuProps {
8
+ path: string;
9
+ additionalNodesProps?: AdditionalNodesProps;
10
+ }
11
+
12
+ export function TenantCpu({path, additionalNodesProps}: TenantCpuProps) {
13
+ return (
14
+ <>
15
+ <TopNodesByLoad path={path} additionalNodesProps={additionalNodesProps} />
16
+ <TopNodesByCpu path={path} additionalNodesProps={additionalNodesProps} />
17
+ <TopShards path={path} />
18
+ <TopQueries path={path} />
19
+ </>
20
+ );
21
+ }
@@ -0,0 +1,53 @@
1
+ import {useDispatch} from 'react-redux';
2
+ import {useCallback} from 'react';
3
+
4
+ import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
5
+ import {
6
+ getTopNodesByCpu,
7
+ selectTopNodesByCpu,
8
+ setDataWasNotLoaded,
9
+ } from '../../../../../store/reducers/tenantOverview/topNodesByCpu/topNodesByCpu';
10
+ import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
11
+ import {getTopNodesByCpuColumns} from '../../../../Nodes/getNodesColumns';
12
+ import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
13
+
14
+ import i18n from '../i18n';
15
+
16
+ interface TopNodesByCpuProps {
17
+ path: string;
18
+ additionalNodesProps?: AdditionalNodesProps;
19
+ }
20
+
21
+ export function TopNodesByCpu({path, additionalNodesProps}: TopNodesByCpuProps) {
22
+ const dispatch = useDispatch();
23
+
24
+ const {wasLoaded, loading, error} = useTypedSelector((state) => state.topNodesByCpu);
25
+ const {autorefresh} = useTypedSelector((state) => state.schema);
26
+ const topNodes = useTypedSelector(selectTopNodesByCpu);
27
+ const columns = getTopNodesByCpuColumns(additionalNodesProps?.getNodeRef);
28
+
29
+ const fetchNodes = useCallback(
30
+ (isBackground) => {
31
+ if (!isBackground) {
32
+ dispatch(setDataWasNotLoaded());
33
+ }
34
+
35
+ dispatch(getTopNodesByCpu({tenant: path}));
36
+ },
37
+ [dispatch, path],
38
+ );
39
+
40
+ useAutofetcher(fetchNodes, [fetchNodes], autorefresh);
41
+
42
+ return (
43
+ <TenantOverviewTableLayout
44
+ data={topNodes || []}
45
+ columns={columns}
46
+ title="Top nodes by pools usage"
47
+ loading={loading}
48
+ wasLoaded={wasLoaded}
49
+ error={error}
50
+ emptyDataMessage={i18n('top-nodes.empty-data')}
51
+ />
52
+ );
53
+ }