ydb-embedded-ui 2.2.1 → 2.4.0

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