ydb-embedded-ui 2.2.1 → 2.4.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 (78) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/assets/icons/shield.svg +3 -0
  3. package/dist/components/Errors/403/AccessDenied.tsx +19 -0
  4. package/dist/components/Errors/403/index.ts +1 -0
  5. package/dist/components/Errors/i18n/en.json +4 -0
  6. package/dist/components/Errors/i18n/index.ts +11 -0
  7. package/dist/components/Errors/i18n/ru.json +4 -0
  8. package/dist/components/NodesViewer/NodesViewer.js +1 -1
  9. package/dist/components/QueryResultTable/QueryResultTable.tsx +16 -21
  10. package/dist/{containers/Storage/StorageFilter/StorageFilter.tsx → components/Search/Search.tsx} +22 -22
  11. package/dist/components/Search/index.ts +1 -0
  12. package/dist/containers/App/App.scss +5 -1
  13. package/dist/containers/Nodes/Nodes.js +6 -1
  14. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +7 -5
  15. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +1 -11
  16. package/dist/containers/Storage/Pdisk/Pdisk.scss +15 -8
  17. package/dist/containers/Storage/Pdisk/Pdisk.tsx +22 -14
  18. package/dist/containers/Storage/Storage.js +39 -50
  19. package/dist/containers/Storage/StorageGroups/StorageGroups.scss +1 -4
  20. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +27 -2
  21. package/dist/containers/Storage/StorageGroups/i18n/en.json +2 -1
  22. package/dist/containers/Storage/StorageGroups/i18n/ru.json +2 -1
  23. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +14 -12
  24. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +7 -5
  25. package/dist/containers/Storage/Vdisk/Vdisk.js +36 -23
  26. package/dist/containers/Storage/Vdisk/Vdisk.scss +6 -0
  27. package/dist/containers/TabletsFilters/TabletsFilters.js +5 -0
  28. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.scss +6 -0
  29. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +82 -0
  30. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/en.json +6 -0
  31. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/index.ts +11 -0
  32. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +6 -0
  33. package/dist/containers/Tenant/Diagnostics/Consumers/index.ts +1 -0
  34. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +7 -0
  35. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +29 -11
  36. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +15 -8
  37. package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +55 -0
  38. package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/index.ts +1 -0
  39. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +5 -5
  40. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +16 -6
  41. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/{IssueViewer.scss → IssueTree.scss} +3 -54
  42. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTree.tsx +87 -0
  43. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.scss +50 -0
  44. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.tsx +25 -0
  45. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/index.ts +1 -0
  46. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +13 -16
  47. package/dist/containers/Tenant/Diagnostics/Healthcheck/{IssuePreview/IssuePreview.tsx → Preview/PreviewItem/PreviewItem.tsx} +6 -8
  48. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +1 -0
  49. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +34 -19
  50. package/dist/containers/Tenant/Preview/Preview.scss +6 -0
  51. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +1 -9
  52. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +2 -2
  53. package/dist/containers/Tenant/QueryEditor/SaveQuery/SaveQuery.js +1 -1
  54. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +27 -20
  55. package/dist/containers/Tenant/Tenant.tsx +14 -16
  56. package/dist/containers/Tenants/Tenants.js +1 -1
  57. package/dist/store/reducers/describe.ts +71 -0
  58. package/dist/store/reducers/healthcheckInfo.ts +123 -0
  59. package/dist/store/reducers/olapStats.js +13 -0
  60. package/dist/store/reducers/schema.js +43 -1
  61. package/dist/store/reducers/storage.js +27 -17
  62. package/dist/store/reducers/tenant.js +3 -1
  63. package/dist/store/utils.ts +21 -13
  64. package/dist/styles/mixins.scss +1 -1
  65. package/dist/types/api/consumers.ts +3 -0
  66. package/dist/types/api/healthcheck.ts +1 -1
  67. package/dist/types/api/storage.ts +35 -10
  68. package/dist/types/store/healthcheck.ts +5 -1
  69. package/dist/types/store/storage.ts +1 -0
  70. package/dist/utils/hooks/useAutofetcher.ts +9 -3
  71. package/package.json +1 -1
  72. package/dist/containers/Storage/StorageFilter/index.ts +0 -1
  73. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuePreview/index.ts +0 -1
  74. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/IssuesList.tsx +0 -62
  75. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/index.ts +0 -1
  76. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssuesViewer.js +0 -151
  77. package/dist/store/reducers/describe.js +0 -45
  78. package/dist/store/reducers/healthcheckInfo.js +0 -45
@@ -1,7 +1,9 @@
1
1
  import _ from 'lodash';
2
2
  import cn from 'bem-cn-lite';
3
3
  import DataTable, {Column, Settings, SortOrder} from '@yandex-cloud/react-data-table';
4
- import {Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
4
+ import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
5
+
6
+ import shieldIcon from '../../../assets/icons/shield.svg';
5
7
 
6
8
  import {Stack} from '../../../components/Stack/Stack';
7
9
  //@ts-ignore
@@ -25,6 +27,7 @@ import './StorageGroups.scss';
25
27
 
26
28
  enum TableColumnsIds {
27
29
  PoolName = 'PoolName',
30
+ Type = 'Type',
28
31
  GroupID = 'GroupID',
29
32
  Used = 'Used',
30
33
  Limit = 'Limit',
@@ -49,6 +52,7 @@ interface StorageGroupsProps {
49
52
 
50
53
  const tableColumnsNames: Record<TableColumnsIdsValues, string> = {
51
54
  PoolName: 'Pool Name',
55
+ Type: 'Type',
52
56
  GroupID: 'Group ID',
53
57
  Used: 'Used',
54
58
  Limit: 'Limit',
@@ -100,7 +104,7 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes, onShowAll}:
100
104
  <div className={b('pool-name-wrapper')}>
101
105
  {splitted && (
102
106
  <Popover
103
- content={<span className={b('tooltip')}>{value as string}</span>}
107
+ content={value as string}
104
108
  placement={['right']}
105
109
  behavior={PopoverBehavior.Immediate}
106
110
  >
@@ -114,6 +118,27 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes, onShowAll}:
114
118
  },
115
119
  align: DataTable.LEFT,
116
120
  },
121
+ {
122
+ name: TableColumnsIds.Type,
123
+ header: tableColumnsNames[TableColumnsIds.Type],
124
+ render: ({value, row}) => (
125
+ <>
126
+ <Label>{value as string || '—'}</Label>
127
+ {' '}
128
+ {row.Encryption && (
129
+ <Popover
130
+ content={i18n('encrypted')}
131
+ placement="right"
132
+ behavior={PopoverBehavior.Immediate}
133
+ >
134
+ <Label>
135
+ <Icon data={shieldIcon} />
136
+ </Label>
137
+ </Popover>
138
+ )}
139
+ </>
140
+ ),
141
+ },
117
142
  {
118
143
  name: TableColumnsIds.Missing,
119
144
  header: tableColumnsNames[TableColumnsIds.Missing],
@@ -2,5 +2,6 @@
2
2
  "empty.default": "No such groups",
3
3
  "empty.out_of_space": "No groups with out of space errors",
4
4
  "empty.degraded": "No degraded groups",
5
- "show_all": "Show all groups"
5
+ "show_all": "Show all groups",
6
+ "encrypted": "Encrypted group"
6
7
  }
@@ -2,5 +2,6 @@
2
2
  "empty.default": "Нет групп",
3
3
  "empty.out_of_space": "Нет групп, в которых кончается место",
4
4
  "empty.degraded": "Нет деградировавших групп",
5
- "show_all": "Показать все группы"
5
+ "show_all": "Показать все группы",
6
+ "encrypted": "Зашифрованная группа"
6
7
  }
@@ -7,27 +7,29 @@
7
7
 
8
8
  min-width: 500px;
9
9
  }
10
- &__pool-name-wrapper {
11
- display: flex;
12
- align-items: flex-end;
10
+ &__pdisks-item {
11
+ flex-grow: 1;
12
+
13
+ max-width: 200px;
14
+ margin-right: 10px;
15
+
16
+ &:last-child {
17
+ margin-right: 0px;
18
+ }
19
+ }
20
+ &__fqdn-wrapper {
21
+ width: 330px;
13
22
  }
14
- &__pool-name {
23
+ &__fqdn {
15
24
  display: inline-block;
16
25
  overflow: hidden;
17
26
 
18
- width: 330px;
19
27
  max-width: 330px;
20
28
 
29
+ vertical-align: top;
21
30
  text-overflow: ellipsis;
22
31
  }
23
32
  &__group-id {
24
33
  font-weight: 500;
25
34
  }
26
- &__tooltip-wrapper {
27
- display: flex;
28
- align-items: center;
29
- }
30
- &__tooltip {
31
- word-break: break-all;
32
- }
33
35
  }
@@ -1,9 +1,9 @@
1
1
  import _ from 'lodash';
2
2
  import cn from 'bem-cn-lite';
3
+
3
4
  import DataTable, {Column, Settings, SortOrder} from '@yandex-cloud/react-data-table';
4
5
  import {Popover, PopoverBehavior} from '@gravity-ui/uikit';
5
6
 
6
- //@ts-ignore
7
7
  import {VisibleEntities} from '../../../store/reducers/storage';
8
8
 
9
9
  import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
@@ -75,13 +75,13 @@ function StorageNodes({data, tableSettings, visibleEntities, onShowAll}: Storage
75
75
  width: 350,
76
76
  render: ({value}) => {
77
77
  return (
78
- <div className={b('tooltip-wrapper')}>
78
+ <div className={b('fqdn-wrapper')}>
79
79
  <Popover
80
- content={<span className={b('tooltip')}>{value as string}</span>}
80
+ content={value as string}
81
81
  placement={['right']}
82
82
  behavior={PopoverBehavior.Immediate}
83
83
  >
84
- <span className={b('pool-name')}>{value as string}</span>
84
+ <span className={b('fqdn')}>{value as string}</span>
85
85
  </Popover>
86
86
  </div>
87
87
  );
@@ -108,7 +108,9 @@ function StorageNodes({data, tableSettings, visibleEntities, onShowAll}: Storage
108
108
  render: ({value, row}) => (
109
109
  <div className={b('pdisks-wrapper')}>
110
110
  {_.map(value as any, (el) => (
111
- <Pdisk key={el.PDiskId} {...el} NodeId={row.NodeId} />
111
+ <div className={b('pdisks-item')}>
112
+ <Pdisk key={el.PDiskId} {...el} NodeId={row.NodeId} />
113
+ </div>
112
114
  ))}
113
115
  </div>
114
116
  ),
@@ -1,17 +1,22 @@
1
1
  import React, {useEffect, useState, useRef, useMemo} from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cn from 'bem-cn-lite';
4
+
4
5
  import {Label, Popup} from '@gravity-ui/uikit';
5
6
 
6
- import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
7
+ import InternalLink from '../../../components/InternalLink/InternalLink';
8
+ import {InfoViewer} from '../../../components/InfoViewer';
9
+
7
10
  import routes, {createHref} from '../../../routes';
8
11
  import {stringifyVdiskId, getPDiskId} from '../../../utils';
9
12
  import {getPDiskType} from '../../../utils/pdisk';
10
- import {InfoViewer} from '../../../components/InfoViewer';
13
+ import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
14
+
15
+ import {STRUCTURE} from '../../Node/NodePages';
16
+
11
17
  import DiskStateProgressBar, {
12
18
  diskProgressColors,
13
19
  } from '../DiskStateProgressBar/DiskStateProgressBar';
14
- import {STRUCTURE} from '../../Node/NodePages';
15
20
 
16
21
  import {colorSeverity, NOT_AVAILABLE_SEVERITY} from '../utils';
17
22
 
@@ -225,33 +230,41 @@ function Vdisk(props) {
225
230
  const available = AvailableSize ? AvailableSize : PDisk?.AvailableSize;
226
231
 
227
232
  if (!available) {
228
- return;
233
+ return undefined;
229
234
  }
230
- return !isNaN(Number(AllocatedSize))
231
- ? (Number(AllocatedSize) * 100) / (Number(available) + Number(AllocatedSize))
232
- : undefined;
235
+
236
+ return isNaN(Number(AllocatedSize))
237
+ ? undefined
238
+ : (Number(AllocatedSize) * 100) / (Number(available) + Number(AllocatedSize));
233
239
  }, [props.AllocatedSize, props.AvailableSize, props.PDisk?.AvailableSize]);
234
240
 
235
241
  return (
236
242
  <React.Fragment>
237
243
  {renderPopup()}
238
244
  <div className={b()} ref={anchor} onMouseEnter={showPopup} onMouseLeave={hidePopup}>
239
- <DiskStateProgressBar
240
- diskAllocatedPercent={vdiskAllocatedPercent}
241
- severity={severity}
242
- href={
243
- props.NodeId
244
- ? createHref(
245
- routes.node,
246
- {id: props.NodeId, activeTab: STRUCTURE},
247
- {
248
- pdiskId: props.PDisk?.PDiskId,
249
- vdiskId: stringifyVdiskId(props.VDiskId),
250
- },
251
- )
252
- : undefined
253
- }
254
- />
245
+ {props.NodeId ? (
246
+ <InternalLink
247
+ to={createHref(
248
+ routes.node,
249
+ {id: props.NodeId, activeTab: STRUCTURE},
250
+ {
251
+ pdiskId: props.PDisk?.PDiskId,
252
+ vdiskId: stringifyVdiskId(props.VDiskId),
253
+ },
254
+ )}
255
+ className={b('content')}
256
+ >
257
+ <DiskStateProgressBar
258
+ diskAllocatedPercent={vdiskAllocatedPercent}
259
+ severity={severity}
260
+ />
261
+ </InternalLink>
262
+ ) : (
263
+ <DiskStateProgressBar
264
+ diskAllocatedPercent={vdiskAllocatedPercent}
265
+ severity={severity}
266
+ />
267
+ )}
255
268
  </div>
256
269
  </React.Fragment>
257
270
  );
@@ -1,4 +1,10 @@
1
1
  .vdisk-storage {
2
+ border-radius: 4px; // to match interactive area with disk shape
3
+
4
+ &__content {
5
+ border-radius: 4px; // to match interactive area with disk shape
6
+ }
7
+
2
8
  &__popup-wrapper {
3
9
  padding: 12px;
4
10
 
@@ -9,6 +9,7 @@ import {Loader, Select} from '@gravity-ui/uikit';
9
9
  import ReactList from 'react-list';
10
10
 
11
11
  import Tablet from '../../components/Tablet/Tablet';
12
+ import {AccessDenied} from '../../components/Errors/403';
12
13
 
13
14
  import {TABLET_COLOR_TO_STATES, TABLETS_STATES} from '../../utils/constants';
14
15
  import {showTooltip, hideTooltip} from '../../store/reducers/tooltip';
@@ -212,6 +213,10 @@ class TabletsFilters extends React.Component {
212
213
  if (loading && !wasLoaded) {
213
214
  return TabletsFilters.renderLoader();
214
215
  } else if (error && typeof error === 'object') {
216
+ if (error.status === 403) {
217
+ return <AccessDenied />;
218
+ }
219
+
215
220
  return <div>{error.statusText}</div>;
216
221
  } else {
217
222
  return this.renderContent();
@@ -0,0 +1,6 @@
1
+ .ydb-consumers {
2
+ &__search {
3
+ width: 200px;
4
+ margin-bottom: 20px;
5
+ }
6
+ }
@@ -0,0 +1,82 @@
1
+ import {useEffect, useState} from 'react';
2
+ import {useDispatch, useSelector} from 'react-redux';
3
+ import block from 'bem-cn-lite';
4
+
5
+ import DataTable, {Column} from '@yandex-cloud/react-data-table';
6
+
7
+ import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
8
+ import {useAutofetcher} from '../../../../utils/hooks';
9
+ import {Search} from '../../../../components/Search';
10
+ import {getDescribe, selectConsumers} from '../../../../store/reducers/describe';
11
+
12
+ import i18n from './i18n';
13
+
14
+ import './Consumers.scss';
15
+
16
+ const b = block('ydb-consumers');
17
+
18
+ interface ConsumersProps {
19
+ path: string;
20
+ }
21
+
22
+ export const Consumers = ({path}: ConsumersProps) => {
23
+ const dispath = useDispatch();
24
+
25
+ const fetchData = () => {
26
+ dispath(getDescribe({path}));
27
+ };
28
+
29
+ useAutofetcher(fetchData, [path]);
30
+
31
+ const consumers = useSelector((state) => selectConsumers(state, path));
32
+
33
+ const [consumersToRender, setConsumersToRender] = useState(consumers);
34
+
35
+ useEffect(() => {
36
+ setConsumersToRender(consumers);
37
+ }, [consumers]);
38
+
39
+ const filterConsumersByName = (search: string) => {
40
+ const filteredConsumers = search
41
+ ? consumers.filter((consumer) => {
42
+ const re = new RegExp(search, 'i');
43
+ return re.test(consumer.name);
44
+ })
45
+ : consumers;
46
+
47
+ setConsumersToRender(filteredConsumers);
48
+ };
49
+
50
+ const handleSearch = (value: string) => {
51
+ filterConsumersByName(value);
52
+ };
53
+
54
+ const columns: Column<any>[] = [
55
+ {
56
+ name: 'name',
57
+ header: i18n('table.columns.consumerName'),
58
+ width: 200,
59
+ },
60
+ ];
61
+
62
+ if (consumers.length === 0) {
63
+ return <div>{i18n('noConsumersMessage')}</div>;
64
+ }
65
+
66
+ return (
67
+ <div className={b()}>
68
+ <Search
69
+ onChange={handleSearch}
70
+ placeholder={i18n('search.placeholder')}
71
+ className={b('search')}
72
+ />
73
+ <DataTable
74
+ theme="yandex-cloud"
75
+ settings={DEFAULT_TABLE_SETTINGS}
76
+ columns={columns}
77
+ data={consumersToRender}
78
+ emptyDataMessage={i18n('table.emptyDataMessage')}
79
+ />
80
+ </div>
81
+ );
82
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "search.placeholder": "Consumer name",
3
+ "table.emptyDataMessage": "No consumers match the current search",
4
+ "table.columns.consumerName": "Consumer",
5
+ "noConsumersMessage": "This topic has no consumers"
6
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-diagnostics-consumers';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,6 @@
1
+ {
2
+ "search.placeholder": "Название читателя",
3
+ "table.emptyDataMessage": "По заданному поиску нет читателей",
4
+ "table.columns.consumerName": "Читатель",
5
+ "noConsumersMessage": "У этого топика нет читателей"
6
+ }
@@ -0,0 +1 @@
1
+ export * from './Consumers';
@@ -5,6 +5,13 @@
5
5
 
6
6
  height: 100%;
7
7
 
8
+ &__loader {
9
+ display: flex;
10
+ flex-grow: 1;
11
+ justify-content: center;
12
+ align-items: center;
13
+ }
14
+
8
15
  &__header-wrapper {
9
16
  padding: 13px 20px 16px;
10
17
 
@@ -5,7 +5,7 @@ import {Link} from 'react-router-dom';
5
5
  import {useDispatch, useSelector} from 'react-redux';
6
6
  import {useLocation} from 'react-router';
7
7
 
8
- import {Switch, Tabs} from '@gravity-ui/uikit';
8
+ import {Loader, Switch, Tabs} from '@gravity-ui/uikit';
9
9
 
10
10
  //@ts-ignore
11
11
  import TopQueries from './TopQueries/TopQueries';
@@ -27,6 +27,7 @@ import Heatmap from '../../Heatmap/Heatmap';
27
27
  import Compute from './Compute/Compute';
28
28
  //@ts-ignore
29
29
  import Tablets from '../../Tablets/Tablets';
30
+ import {Consumers} from './Consumers';
30
31
 
31
32
  import routes, {createHref} from '../../../routes';
32
33
  import type {EPathType} from '../../../types/api/schema';
@@ -53,9 +54,9 @@ function Diagnostics(props: DiagnosticsProps) {
53
54
  currentSchema: currentItem = {},
54
55
  autorefresh,
55
56
  } = useSelector((state: any) => state.schema);
56
- const {
57
- diagnosticsTab = GeneralPagesIds.overview,
58
- } = useSelector((state: any) => state.tenant);
57
+ const {diagnosticsTab = GeneralPagesIds.overview, wasLoaded} = useSelector(
58
+ (state: any) => state.tenant,
59
+ );
59
60
 
60
61
  const location = useLocation();
61
62
 
@@ -79,14 +80,17 @@ function Diagnostics(props: DiagnosticsProps) {
79
80
  dispatch(setDiagnosticsTab(tab));
80
81
  };
81
82
  const activeTab = useMemo(() => {
82
- if (pages.find((el) => el.id === diagnosticsTab)) {
83
- return diagnosticsTab;
84
- } else {
85
- const newPage = pages[0].id;
86
- forwardToDiagnosticTab(newPage);
87
- return newPage;
83
+ if (wasLoaded) {
84
+ if (pages.find((el) => el.id === diagnosticsTab)) {
85
+ return diagnosticsTab;
86
+ } else {
87
+ const newPage = pages[0].id;
88
+ forwardToDiagnosticTab(newPage);
89
+ return newPage;
90
+ }
88
91
  }
89
- }, [pages, diagnosticsTab]);
92
+ return undefined;
93
+ }, [pages, diagnosticsTab, wasLoaded]);
90
94
 
91
95
  const onAutorefreshToggle = (value: boolean) => {
92
96
  if (value) {
@@ -148,6 +152,9 @@ function Diagnostics(props: DiagnosticsProps) {
148
152
  case GeneralPagesIds.graph: {
149
153
  return <Heatmap path={currentItem.Path} />;
150
154
  }
155
+ case GeneralPagesIds.consumers: {
156
+ return <Consumers path={currentItem.Path} />;
157
+ }
151
158
  default: {
152
159
  return <div>No data...</div>;
153
160
  }
@@ -185,6 +192,17 @@ function Diagnostics(props: DiagnosticsProps) {
185
192
  );
186
193
  };
187
194
 
195
+ // Loader prevents incorrect loading of tabs
196
+ // After tabs are initially loaded it is no longer needed
197
+ // Thus there is no also "loading" check as in other parts of the project
198
+ if (!wasLoaded) {
199
+ return (
200
+ <div className={b('loader')}>
201
+ <Loader size="l" />
202
+ </div>
203
+ );
204
+ }
205
+
188
206
  return (
189
207
  <div className={b()}>
190
208
  {renderTabs()}
@@ -1,4 +1,4 @@
1
- import {EPathType} from "../../../types/api/schema";
1
+ import {EPathType} from '../../../types/api/schema';
2
2
 
3
3
  export enum GeneralPagesIds {
4
4
  'overview' = 'Overview',
@@ -11,11 +11,12 @@ export enum GeneralPagesIds {
11
11
  'describe' = 'Describe',
12
12
  'hotKeys' = 'hotKeys',
13
13
  'graph' = 'graph',
14
+ 'consumers' = 'consumers',
14
15
  }
15
16
 
16
17
  type Page = {
17
- id: GeneralPagesIds,
18
- title: string,
18
+ id: GeneralPagesIds;
19
+ title: string;
19
20
  };
20
21
 
21
22
  const overview = {
@@ -66,6 +67,11 @@ const graph = {
66
67
  title: 'Graph',
67
68
  };
68
69
 
70
+ const consumers = {
71
+ id: GeneralPagesIds.consumers,
72
+ title: 'Consumers',
73
+ };
74
+
69
75
  export const DATABASE_PAGES = [
70
76
  overview,
71
77
  topQueries,
@@ -81,7 +87,8 @@ export const TABLE_PAGES = [overview, topShards, graph, tablets, hotKeys, descri
81
87
 
82
88
  export const DIR_PAGES = [overview, topShards, describe];
83
89
 
84
- export const TOPIC_PAGES = [overview, describe];
90
+ export const CDC_STREAM_PAGES = [overview, describe];
91
+ export const TOPIC_PAGES = [overview, consumers, describe];
85
92
 
86
93
  // verbose mapping to guarantee correct tabs for new path types
87
94
  // TS will error when a new type is added but not mapped here
@@ -97,10 +104,10 @@ const pathTypeToPages: Record<EPathType, Page[] | undefined> = {
97
104
 
98
105
  [EPathType.EPathTypeDir]: DIR_PAGES,
99
106
  [EPathType.EPathTypeTableIndex]: DIR_PAGES,
100
-
101
- [EPathType.EPathTypeCdcStream]: TOPIC_PAGES,
107
+
108
+ [EPathType.EPathTypeCdcStream]: CDC_STREAM_PAGES,
109
+
102
110
  [EPathType.EPathTypePersQueueGroup]: TOPIC_PAGES,
103
111
  };
104
112
 
105
- export const getPagesByType = (type?: EPathType) =>
106
- (type && pathTypeToPages[type]) || DIR_PAGES;
113
+ export const getPagesByType = (type?: EPathType) => (type && pathTypeToPages[type]) || DIR_PAGES;
@@ -0,0 +1,55 @@
1
+ import cn from 'bem-cn-lite';
2
+
3
+ import {Button, Icon} from '@gravity-ui/uikit';
4
+
5
+ import updateArrow from '../../../../../assets/icons/update-arrow.svg';
6
+
7
+ import type {IIssuesTree} from '../../../../../types/store/healthcheck';
8
+
9
+ import IssueTree from '../IssuesViewer/IssueTree';
10
+
11
+ import i18n from '../i18n';
12
+
13
+ const b = cn('healthcheck');
14
+
15
+ interface DetailsProps {
16
+ issueTree?: IIssuesTree;
17
+ loading?: boolean;
18
+ onUpdate: VoidFunction;
19
+ }
20
+
21
+ export const Details = (props: DetailsProps) => {
22
+ const {loading, onUpdate, issueTree} = props;
23
+
24
+ if (!issueTree) {
25
+ return null;
26
+ }
27
+
28
+ const renderHealthcheckHeader = () => {
29
+ return (
30
+ <div className={b('details-header')}>
31
+ <h3 className={b('details-header-title')}>{i18n('title.healthcheck')}</h3>
32
+ <div className={b('details-header-update')}>
33
+ <Button size="s" onClick={onUpdate} loading={loading} view="flat-secondary">
34
+ <Icon data={updateArrow} height={20} width={20} />
35
+ </Button>
36
+ </div>
37
+ </div>
38
+ );
39
+ };
40
+
41
+ const renderHealthcheckIssues = () => {
42
+ return (
43
+ <div className={b('issues-wrapper')}>
44
+ <IssueTree issueTree={issueTree} />
45
+ </div>
46
+ );
47
+ };
48
+
49
+ return (
50
+ <div className={b('details')}>
51
+ {renderHealthcheckHeader()}
52
+ {renderHealthcheckIssues()}
53
+ </div>
54
+ );
55
+ };
@@ -0,0 +1 @@
1
+ export * from './Details';
@@ -7,7 +7,7 @@
7
7
  // Thus we will get rid of unneeded layout shift when scrollbar appear
8
8
  min-width: 885px;
9
9
 
10
- &__issues-list {
10
+ &__details {
11
11
  padding: 25px 20px 20px;
12
12
  }
13
13
 
@@ -15,7 +15,7 @@
15
15
  margin-bottom: 15px;
16
16
  }
17
17
 
18
- &__issues {
18
+ &__issues-wrapper {
19
19
  overflow-x: hidden;
20
20
  overflow-y: auto;
21
21
 
@@ -32,20 +32,20 @@
32
32
  padding: 15px 0;
33
33
  }
34
34
 
35
- &__issues-list-header {
35
+ &__details-header {
36
36
  display: flex;
37
37
  align-items: center;
38
38
 
39
39
  margin-bottom: 20px;
40
40
  }
41
41
 
42
- &__issues-list-header-title {
42
+ &__details-header-title {
43
43
  margin: 0 10px 0 0;
44
44
 
45
45
  @include text-header-1();
46
46
  }
47
47
 
48
- &__issues-list-header-update {
48
+ &__details-header-update {
49
49
  margin-left: 10px;
50
50
  }
51
51