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
@@ -9,7 +9,7 @@ import {Loader} from '@gravity-ui/uikit';
9
9
 
10
10
  import {DateRange, DateRangeValues} from '../../../../components/DateRange';
11
11
  import {Search} from '../../../../components/Search';
12
- import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery';
12
+ import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery';
13
13
 
14
14
  import {changeUserInput} from '../../../../store/reducers/executeQuery';
15
15
  import {
@@ -23,7 +23,11 @@ import type {EPathType} from '../../../../types/api/schema';
23
23
  import type {ITopQueriesFilters} from '../../../../types/store/executeTopQueries';
24
24
  import type {IQueryResult} from '../../../../types/store/query';
25
25
 
26
- import {TENANT_PAGE, TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
26
+ import {
27
+ TENANT_PAGE,
28
+ TENANT_PAGES_IDS,
29
+ TENANT_QUERY_TABS_ID,
30
+ } from '../../../../store/reducers/tenant/constants';
27
31
  import {formatDateTime, formatNumber} from '../../../../utils';
28
32
  import {HOUR_IN_SECONDS} from '../../../../utils/constants';
29
33
  import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
@@ -50,7 +54,10 @@ const COLUMNS: Column<KeyValueRow>[] = [
50
54
  sortable: false,
51
55
  render: ({row}) => (
52
56
  <div className={b('query')}>
53
- <TruncatedQuery value={row.QueryText} maxQueryHeight={MAX_QUERY_HEIGHT} />
57
+ <TruncatedQuery
58
+ value={row.QueryText?.toString()}
59
+ maxQueryHeight={MAX_QUERY_HEIGHT}
60
+ />
54
61
  </div>
55
62
  ),
56
63
  },
@@ -1,4 +1,4 @@
1
- @import '../../../styles/mixins.scss';
1
+ @import '../../../../styles/mixins.scss';
2
2
 
3
3
  .kv-preview {
4
4
  height: 100%;
@@ -0,0 +1,121 @@
1
+ import {useCallback} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
+ import cn from 'bem-cn-lite';
4
+
5
+ import {Loader, Button} from '@gravity-ui/uikit';
6
+
7
+ import type {EPathType} from '../../../../types/api/schema';
8
+ import {sendQuery, setQueryOptions} from '../../../../store/reducers/preview';
9
+ import {setShowPreview} from '../../../../store/reducers/schema/schema';
10
+ import {prepareQueryError} from '../../../../utils/query';
11
+ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
12
+
13
+ import {Icon} from '../../../../components/Icon';
14
+ import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
15
+ import {QueryResultTable} from '../../../../components/QueryResultTable';
16
+ import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
17
+
18
+ import {isTableType} from '../../utils/schema';
19
+
20
+ import i18n from '../i18n';
21
+
22
+ import './Preview.scss';
23
+
24
+ const b = cn('kv-preview');
25
+
26
+ interface PreviewProps {
27
+ database: string;
28
+ type: EPathType | undefined;
29
+ }
30
+
31
+ export const Preview = ({database, type}: PreviewProps) => {
32
+ const dispatch = useDispatch();
33
+
34
+ const {data = {}, loading, error, wasLoaded} = useTypedSelector((state) => state.preview);
35
+ const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
36
+ const isFullscreen = useTypedSelector((state) => state.fullscreen);
37
+
38
+ const sendQueryForPreview = useCallback(
39
+ (isBackground) => {
40
+ if (!isTableType(type)) {
41
+ return;
42
+ }
43
+
44
+ if (!isBackground) {
45
+ dispatch(
46
+ setQueryOptions({
47
+ wasLoaded: false,
48
+ data: undefined,
49
+ }),
50
+ );
51
+ }
52
+
53
+ const query = `--!syntax_v1\nselect * from \`${currentSchemaPath}\` limit 32`;
54
+
55
+ dispatch(
56
+ sendQuery({
57
+ query,
58
+ database,
59
+ action: 'execute-scan',
60
+ }),
61
+ );
62
+ },
63
+ [dispatch, database, currentSchemaPath, type],
64
+ );
65
+
66
+ useAutofetcher(sendQueryForPreview, [sendQueryForPreview], autorefresh);
67
+
68
+ const handleClosePreview = () => {
69
+ dispatch(setShowPreview(false));
70
+ };
71
+
72
+ const renderHeader = () => {
73
+ return (
74
+ <div className={b('header')}>
75
+ <div className={b('title')}>
76
+ {i18n('preview.title')}{' '}
77
+ <div className={b('table-name')}>{currentSchemaPath}</div>
78
+ </div>
79
+ <div className={b('controls-left')}>
80
+ <EnableFullscreenButton disabled={Boolean(error)} />
81
+ <Button
82
+ view="flat-secondary"
83
+ onClick={handleClosePreview}
84
+ title={i18n('preview.close')}
85
+ >
86
+ <Icon name="close" viewBox={'0 0 16 16'} width={16} height={16} />
87
+ </Button>
88
+ </div>
89
+ </div>
90
+ );
91
+ };
92
+
93
+ if (loading && !wasLoaded) {
94
+ return (
95
+ <div className={b('loader-container')}>
96
+ <Loader size="m" />
97
+ </div>
98
+ );
99
+ }
100
+
101
+ let message;
102
+
103
+ if (!isTableType(type)) {
104
+ message = <div className={b('message-container')}>{i18n('preview.not-available')}</div>;
105
+ } else if (error) {
106
+ message = <div className={b('message-container')}>{prepareQueryError(error)}</div>;
107
+ }
108
+
109
+ const content = message ?? (
110
+ <div className={b('result')}>
111
+ <QueryResultTable data={data.result} columns={data.columns} />
112
+ </div>
113
+ );
114
+
115
+ return (
116
+ <div className={b()}>
117
+ {renderHeader()}
118
+ {isFullscreen ? <Fullscreen>{content}</Fullscreen> : content}
119
+ </div>
120
+ );
121
+ };
@@ -3,7 +3,7 @@ import block from 'bem-cn-lite';
3
3
 
4
4
  import DataTable, {Column} from '@gravity-ui/react-data-table';
5
5
 
6
- import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery';
6
+ import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery';
7
7
  import {setQueryTab} from '../../../../store/reducers/tenant/tenant';
8
8
  import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
9
9
  import {useTypedSelector} from '../../../../utils/hooks';
@@ -35,7 +35,7 @@ import {
35
35
  PaneVisibilityActionTypes,
36
36
  paneVisibilityToggleReducerCreator,
37
37
  } from '../../utils/paneVisibilityToggleHelpers';
38
- import Preview from '../../Preview/Preview';
38
+ import {Preview} from '../Preview/Preview';
39
39
 
40
40
  import {ExecuteResult} from '../ExecuteResult/ExecuteResult';
41
41
  import {ExplainResult} from '../ExplainResult/ExplainResult';
@@ -104,8 +104,10 @@ function QueryEditor(props) {
104
104
 
105
105
  useEffect(() => {
106
106
  if (savedPath !== path) {
107
+ if (savedPath) {
108
+ changeUserInput({input: ''});
109
+ }
107
110
  setTenantPath(path);
108
- changeUserInput({input: ''});
109
111
  }
110
112
  }, [changeUserInput, setTenantPath, path, savedPath]);
111
113
 
@@ -380,12 +382,8 @@ function QueryEditor(props) {
380
382
  };
381
383
 
382
384
  const renderPreview = () => {
383
- const {path, type, currentSchema = {}} = props;
384
- const partCount = currentSchema?.PathDescription?.TableStats?.PartCount;
385
- // onExpandResultHandler();
386
- return (
387
- <Preview database={path} table={currentSchema.Path} type={type} partCount={partCount} />
388
- );
385
+ const {path, type} = props;
386
+ return <Preview database={path} type={type} />;
389
387
  };
390
388
 
391
389
  const handlePreviousHistoryClick = () => {
@@ -10,7 +10,7 @@ import {setQueryNameToEdit} from '../../../../store/reducers/saveQuery';
10
10
  import {setQueryTab} from '../../../../store/reducers/tenant/tenant';
11
11
  import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
12
12
 
13
- import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery';
13
+ import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery';
14
14
  import {IconWrapper} from '../../../../components/Icon';
15
15
 
16
16
  import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants';
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "controls.query-mode-selector_type": "Query type:",
3
+
3
4
  "tabs.newQuery": "New query",
4
5
  "tabs.history": "History",
5
6
  "tabs.saved": "Saved",
7
+
6
8
  "history.empty": "History is empty",
7
9
  "saved.empty": "There are no saved queries",
10
+
8
11
  "delete-dialog.header": "Delete query",
9
12
  "delete-dialog.question": "Are you sure you want to delete query",
10
13
  "delete-dialog.delete": "Delete",
11
- "delete-dialog.cancel": "Cancel"
14
+ "delete-dialog.cancel": "Cancel",
15
+
16
+ "preview.title": "Preview",
17
+ "preview.not-available": "Preview is not available",
18
+ "preview.close": "Close preview"
12
19
  }
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "controls.query-mode-selector_type": "Тип запроса:",
3
+
3
4
  "tabs.newQuery": "Новый запрос",
4
5
  "tabs.history": "История",
5
6
  "tabs.saved": "Сохраненные",
7
+
6
8
  "history.empty": "История пуста",
7
9
  "saved.empty": "Нет сохраненных запросов",
10
+
8
11
  "delete-dialog.header": "Удалить запрос",
9
12
  "delete-dialog.question": "Вы уверены что хотите удалить запрос",
10
13
  "delete-dialog.delete": "Удалить",
11
- "delete-dialog.cancel": "Отменить"
14
+ "delete-dialog.cancel": "Отменить",
15
+
16
+ "preview.title": "Предпросмотр",
17
+ "preview.not-available": "Предпросмотр недоступен",
18
+ "preview.close": "Закрыть предпросмотр"
12
19
  }
@@ -0,0 +1,269 @@
1
+ import cn from 'bem-cn-lite';
2
+ import {useDispatch} from 'react-redux';
3
+
4
+ import DataTable, {Column} from '@gravity-ui/react-data-table';
5
+ import {Loader, Button} from '@gravity-ui/uikit';
6
+
7
+ import EntityStatus from '../../components/EntityStatus/EntityStatus';
8
+ import {PoolsGraph} from '../../components/PoolsGraph/PoolsGraph';
9
+ import {TabletsStatistic} from '../../components/TabletsStatistic';
10
+ import {ProblemFilter} from '../../components/ProblemFilter';
11
+ import {Illustration} from '../../components/Illustration';
12
+ import {Search} from '../../components/Search';
13
+ import {ResponseError} from '../../components/Errors/ResponseError';
14
+
15
+ import type {AdditionalTenantsProps} from '../../types/additionalProps';
16
+ import type {ProblemFilterValue} from '../../store/reducers/settings/types';
17
+ import type {PreparedTenant} from '../../store/reducers/tenants/types';
18
+ import {getTenantsInfo, setSearchValue} from '../../store/reducers/tenants/tenants';
19
+ import {
20
+ selectFilteredTenants,
21
+ selectTenantsSearchValue,
22
+ } from '../../store/reducers/tenants/selectors';
23
+ import {
24
+ changeFilter,
25
+ ProblemFilterValues,
26
+ selectProblemFilter,
27
+ } from '../../store/reducers/settings/settings';
28
+ import {formatCPU, formatBytesToGigabyte, formatNumber} from '../../utils';
29
+ import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
30
+ import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
31
+ import {clusterName} from '../../store';
32
+
33
+ import {TenantTabsGroups, TENANT_INFO_TABS, getTenantPath} from '../Tenant/TenantPages';
34
+
35
+ import './Tenants.scss';
36
+
37
+ const b = cn('tenants');
38
+
39
+ interface TenantsProps {
40
+ additionalTenantsProps?: AdditionalTenantsProps;
41
+ }
42
+
43
+ export const Tenants = ({additionalTenantsProps}: TenantsProps) => {
44
+ const dispatch = useDispatch();
45
+
46
+ const {error, loading, wasLoaded} = useTypedSelector((state) => state.tenants);
47
+ const searchValue = useTypedSelector(selectTenantsSearchValue);
48
+ const filteredTenants = useTypedSelector(selectFilteredTenants);
49
+ const problemFilter = useTypedSelector(selectProblemFilter);
50
+
51
+ useAutofetcher(
52
+ () => {
53
+ dispatch(getTenantsInfo(clusterName));
54
+ },
55
+ [dispatch],
56
+ true,
57
+ );
58
+
59
+ const handleProblemFilterChange = (value: string) => {
60
+ dispatch(changeFilter(value as ProblemFilterValue));
61
+ };
62
+
63
+ const handleSearchChange = (value: string) => {
64
+ dispatch(setSearchValue(value));
65
+ };
66
+
67
+ const renderControls = () => {
68
+ return (
69
+ <div className={b('controls')}>
70
+ <Search
71
+ value={searchValue}
72
+ onChange={handleSearchChange}
73
+ placeholder="Database name"
74
+ className={b('search')}
75
+ />
76
+ <ProblemFilter value={problemFilter} onChange={handleProblemFilterChange} />
77
+ </div>
78
+ );
79
+ };
80
+
81
+ const renderTable = () => {
82
+ const initialTenantInfoTab = TENANT_INFO_TABS[0].id;
83
+
84
+ const getTenantBackend = (tenant: PreparedTenant) => {
85
+ const backend = tenant.MonitoringEndpoint ?? tenant.backend;
86
+ return additionalTenantsProps?.prepareTenantBackend?.(backend);
87
+ };
88
+
89
+ const columns: Column<PreparedTenant>[] = [
90
+ {
91
+ name: 'Name',
92
+ header: 'Database',
93
+ render: ({row}) => {
94
+ const backend = getTenantBackend(row);
95
+ const isExternalLink = Boolean(backend);
96
+ return (
97
+ <div className={b('name-wrapper')}>
98
+ <EntityStatus
99
+ externalLink={isExternalLink}
100
+ className={b('name')}
101
+ name={row.Name || 'unknown database'}
102
+ withLeftTrim={true}
103
+ status={row.Overall}
104
+ hasClipboardButton
105
+ path={getTenantPath({
106
+ name: row.Name,
107
+ backend,
108
+ [TenantTabsGroups.info]: initialTenantInfoTab,
109
+ })}
110
+ />
111
+ {additionalTenantsProps?.getMonitoringLink?.(row.Name, row.Type)}
112
+ </div>
113
+ );
114
+ },
115
+ width: 440,
116
+ sortable: true,
117
+ defaultOrder: DataTable.DESCENDING,
118
+ },
119
+ {
120
+ name: 'controlPlaneName',
121
+ header: 'Name',
122
+ render: ({row}) => {
123
+ return row.controlPlaneName;
124
+ },
125
+ width: 200,
126
+ sortable: true,
127
+ defaultOrder: DataTable.DESCENDING,
128
+ },
129
+ {
130
+ name: 'Type',
131
+ width: 200,
132
+ render: ({row}) => {
133
+ if (row.Type !== 'Serverless') {
134
+ return row.Type;
135
+ }
136
+
137
+ return (
138
+ <div className={b('type')}>
139
+ <span className={b('type-value')}>{row.Type}</span>
140
+ <Button
141
+ className={b('type-button')}
142
+ onClick={() => handleSearchChange(row.sharedTenantName || '')}
143
+ >
144
+ Show shared
145
+ </Button>
146
+ </div>
147
+ );
148
+ },
149
+ },
150
+ {
151
+ name: 'State',
152
+ width: 90,
153
+ render: ({row}) => (row.State ? row.State.toLowerCase() : '—'),
154
+ customStyle: () => ({textTransform: 'capitalize'}),
155
+ },
156
+ {
157
+ name: 'cpu',
158
+ header: 'CPU',
159
+ width: 80,
160
+ render: ({row}) => {
161
+ // Don't show values below 0.01 when formatted
162
+ if (row.cpu > 10_000) {
163
+ return formatCPU(row.cpu);
164
+ }
165
+ return '—';
166
+ },
167
+ align: DataTable.RIGHT,
168
+ defaultOrder: DataTable.DESCENDING,
169
+ },
170
+ {
171
+ name: 'memory',
172
+ header: 'Memory',
173
+ width: 120,
174
+ render: ({row}) => (row.memory ? formatBytesToGigabyte(row.memory) : '—'),
175
+ align: DataTable.RIGHT,
176
+ defaultOrder: DataTable.DESCENDING,
177
+ },
178
+ {
179
+ name: 'storage',
180
+ header: 'Storage',
181
+ width: 120,
182
+ render: ({row}) => (row.storage ? formatBytesToGigabyte(row.storage) : '—'),
183
+ align: DataTable.RIGHT,
184
+ defaultOrder: DataTable.DESCENDING,
185
+ },
186
+ {
187
+ name: 'nodesCount',
188
+ header: 'Nodes',
189
+ width: 100,
190
+ render: ({row}) => (row.nodesCount ? formatNumber(row.nodesCount) : '—'),
191
+ align: DataTable.RIGHT,
192
+ defaultOrder: DataTable.DESCENDING,
193
+ },
194
+ {
195
+ name: 'groupsCount',
196
+ header: 'Groups',
197
+ width: 100,
198
+ render: ({row}) => (row.groupsCount ? formatNumber(row.groupsCount) : '—'),
199
+ align: DataTable.RIGHT,
200
+ defaultOrder: DataTable.DESCENDING,
201
+ },
202
+ {
203
+ name: 'PoolStats',
204
+ header: 'Pools',
205
+ width: 100,
206
+ sortAccessor: ({PoolStats = []}) =>
207
+ PoolStats.reduce((acc, item) => acc + (item.Usage || 0), 0),
208
+ defaultOrder: DataTable.DESCENDING,
209
+ align: DataTable.LEFT,
210
+ render: ({row}) => <PoolsGraph pools={row.PoolStats} />,
211
+ },
212
+ {
213
+ name: 'Tablets',
214
+ header: 'Tablets States',
215
+ sortable: false,
216
+ width: 430,
217
+ render: ({row}) => {
218
+ const backend = getTenantBackend(row);
219
+
220
+ return row.Tablets ? (
221
+ <TabletsStatistic
222
+ path={row.Name}
223
+ tablets={row.Tablets}
224
+ nodeIds={row.NodeIds || []}
225
+ backend={backend}
226
+ />
227
+ ) : (
228
+ '—'
229
+ );
230
+ },
231
+ },
232
+ ];
233
+
234
+ if (filteredTenants.length === 0 && problemFilter !== ProblemFilterValues.ALL) {
235
+ return <Illustration name="thumbsUp" width="200" />;
236
+ }
237
+
238
+ return (
239
+ <div className={b('table-wrapper')}>
240
+ <DataTable
241
+ theme="yandex-cloud"
242
+ data={filteredTenants}
243
+ columns={columns}
244
+ settings={DEFAULT_TABLE_SETTINGS}
245
+ emptyDataMessage="No such tenants"
246
+ />
247
+ </div>
248
+ );
249
+ };
250
+
251
+ if (loading && !wasLoaded) {
252
+ return (
253
+ <div className={'loader'}>
254
+ <Loader size="l" />
255
+ </div>
256
+ );
257
+ }
258
+
259
+ if (error) {
260
+ return <ResponseError error={error} />;
261
+ }
262
+
263
+ return (
264
+ <div className={b()}>
265
+ {renderControls()}
266
+ {renderTable()}
267
+ </div>
268
+ );
269
+ };
@@ -282,19 +282,24 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
282
282
  },
283
283
  {concurrentId}: AxiosOptions = {},
284
284
  ) {
285
+ // Time difference to ensure that timeout from ui will be shown rather than backend error
286
+ const uiTimeout = 9 * 60 * 1000;
287
+ const backendTimeout = 10 * 60 * 1000;
288
+
285
289
  return this.post<QueryAPIResponse<Action, Schema>>(
286
- this.getPath(`/viewer/json/query${schema ? `?schema=${schema}` : ''}`),
290
+ this.getPath(
291
+ `/viewer/json/query?timeout=${backendTimeout}${schema ? `&schema=${schema}` : ''}`,
292
+ ),
287
293
  {
288
294
  query,
289
295
  database,
290
296
  action,
291
297
  stats,
292
- timeout: 600000,
293
298
  },
294
299
  {},
295
300
  {
296
301
  concurrentId,
297
- timeout: 9 * 60 * 1000,
302
+ timeout: uiTimeout,
298
303
  },
299
304
  );
300
305
  }
@@ -2,13 +2,13 @@ import type {Reducer} from 'redux';
2
2
  import {createSelector, Selector} from 'reselect';
3
3
  import {escapeRegExp} from 'lodash/fp';
4
4
 
5
- import type {ValueOf} from '../../../types/common';
6
5
  import '../../../services/api';
7
6
  import {HOUR_IN_SECONDS} from '../../../utils/constants';
8
7
  import {calcUptime, calcUptimeInSeconds} from '../../../utils';
9
8
  import {NodesUptimeFilterValues} from '../../../utils/nodes';
10
9
  import {EFlag} from '../../../types/api/enums';
11
10
 
11
+ import type {ProblemFilterValue} from '../settings/types';
12
12
  import {createRequestActionTypes, createApiRequest} from '../../utils';
13
13
  import {ProblemFilterValues} from '../settings/settings';
14
14
 
@@ -181,7 +181,7 @@ const getNodesList = (state: NodesStateSlice) => state.nodes.data;
181
181
 
182
182
  const filterNodesByProblemsStatus = (
183
183
  nodesList: NodesPreparedEntity[] = [],
184
- problemFilter: ValueOf<typeof ProblemFilterValues>,
184
+ problemFilter: ProblemFilterValue,
185
185
  ) => {
186
186
  if (problemFilter === ProblemFilterValues.ALL) {
187
187
  return nodesList;
@@ -192,8 +192,8 @@ const filterNodesByProblemsStatus = (
192
192
  });
193
193
  };
194
194
 
195
- export const filterNodesByUptime = (
196
- nodesList: NodesPreparedEntity[] = [],
195
+ export const filterNodesByUptime = <T extends {StartTime?: string}>(
196
+ nodesList: T[] = [],
197
197
  nodesUptimeFilter: NodesUptimeFilterValues,
198
198
  ) => {
199
199
  if (nodesUptimeFilter === NodesUptimeFilterValues.All) {
@@ -1,5 +1,5 @@
1
1
  import type {IResponseError} from '../../../types/api/error';
2
- import type {IProcessSpeedStats} from '../../../utils/bytesParsers';
2
+ import type {ProcessSpeedStats} from '../../../utils/bytesParsers';
3
3
  import type {ApiRequestAction} from '../../utils';
4
4
 
5
5
  import {FETCH_PARTITIONS, setDataWasNotLoaded, setSelectedConsumer} from './partitions';
@@ -9,8 +9,8 @@ export interface PreparedPartitionData {
9
9
  partitionId: string;
10
10
  storeSize: string;
11
11
 
12
- writeSpeed: IProcessSpeedStats;
13
- readSpeed?: IProcessSpeedStats;
12
+ writeSpeed: ProcessSpeedStats;
13
+ readSpeed?: ProcessSpeedStats;
14
14
 
15
15
  partitionWriteLag: number;
16
16
  partitionWriteIdleTime: number;
@@ -1,7 +1,6 @@
1
1
  import type {Reducer} from 'redux';
2
2
  import type {ThunkAction} from 'redux-thunk';
3
3
 
4
- import type {ValueOf} from '../../../types/common';
5
4
  import {
6
5
  SAVED_QUERIES_KEY,
7
6
  THEME_KEY,
@@ -23,6 +22,7 @@ import {TENANT_PAGES_IDS} from '../tenant/constants';
23
22
 
24
23
  import type {RootState} from '..';
25
24
  import type {
25
+ ProblemFilterValue,
26
26
  SetSettingValueAction,
27
27
  SettingsAction,
28
28
  SettingsRootStateSlice,
@@ -133,11 +133,13 @@ export const getParsedSettingValue = (state: SettingsRootStateSlice, name: strin
133
133
  return parseJson(value);
134
134
  };
135
135
 
136
- export const changeFilter = (filter: ValueOf<typeof ProblemFilterValues>) => {
136
+ export const changeFilter = (filter: ProblemFilterValue) => {
137
137
  return {
138
138
  type: CHANGE_PROBLEM_FILTER,
139
139
  data: filter,
140
140
  } as const;
141
141
  };
142
142
 
143
+ export const selectProblemFilter = (state: SettingsRootStateSlice) => state.settings.problemFilter;
144
+
143
145
  export default settings;
@@ -1,8 +1,10 @@
1
1
  import type {ValueOf} from '../../../types/common';
2
2
  import {changeFilter, ProblemFilterValues, SET_SETTING_VALUE} from './settings';
3
3
 
4
+ export type ProblemFilterValue = ValueOf<typeof ProblemFilterValues>;
5
+
4
6
  export interface SettingsState {
5
- problemFilter: ValueOf<typeof ProblemFilterValues>;
7
+ problemFilter: ProblemFilterValue;
6
8
  userSettings: Record<string, string | undefined>;
7
9
  systemSettings: Record<string, string | undefined>;
8
10
  }