ydb-embedded-ui 4.9.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
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
  }