ydb-embedded-ui 4.5.2 → 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/assets/icons/versions.svg +3 -0
  3. package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +7 -2
  4. package/dist/components/Tablet/Tablet.tsx +17 -3
  5. package/dist/components/TabletsStatistic/TabletsStatistic.tsx +23 -16
  6. package/dist/containers/App/Content.js +8 -4
  7. package/dist/containers/AsideNavigation/AsideNavigation.tsx +4 -50
  8. package/dist/containers/Cluster/Cluster.scss +7 -48
  9. package/dist/containers/Cluster/Cluster.tsx +129 -20
  10. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +34 -17
  11. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +58 -92
  12. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.scss +48 -0
  13. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +34 -0
  14. package/dist/containers/Cluster/utils.tsx +45 -0
  15. package/dist/containers/Header/Header.scss +4 -19
  16. package/dist/containers/Header/Header.tsx +72 -46
  17. package/dist/containers/Header/breadcrumbs.ts +146 -0
  18. package/dist/containers/Node/Node.tsx +25 -29
  19. package/dist/containers/Node/NodePages.ts +10 -6
  20. package/dist/containers/Nodes/Nodes.tsx +0 -16
  21. package/dist/containers/Nodes/getNodesColumns.tsx +1 -1
  22. package/dist/containers/Storage/Storage.js +1 -11
  23. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +11 -3
  24. package/dist/containers/Tablet/Tablet.tsx +40 -4
  25. package/dist/containers/Tablet/TabletInfo/TabletInfo.tsx +2 -2
  26. package/dist/containers/TabletsFilters/TabletsFilters.js +15 -2
  27. package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +1 -1
  28. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +1 -1
  29. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +7 -0
  30. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -4
  31. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +5 -3
  32. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +1 -1
  33. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +4 -6
  34. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +56 -53
  35. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +2 -1
  36. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +11 -13
  37. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +1 -1
  38. package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +2 -2
  39. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +7 -3
  40. package/dist/containers/Tenant/Preview/Preview.js +1 -1
  41. package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.js → Query/ExecuteResult/ExecuteResult.js} +3 -5
  42. package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.scss → Query/ExecuteResult/ExecuteResult.scss} +1 -1
  43. package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.js → Query/ExplainResult/ExplainResult.js} +3 -5
  44. package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.scss → Query/ExplainResult/ExplainResult.scss} +1 -1
  45. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +20 -0
  46. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +60 -0
  47. package/dist/containers/Tenant/Query/Query.scss +16 -0
  48. package/dist/containers/Tenant/Query/Query.tsx +73 -0
  49. package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.js +43 -100
  50. package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.scss +7 -23
  51. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/OldQueryEditorControls.tsx +10 -3
  52. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.scss +1 -4
  53. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.tsx +8 -1
  54. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/shared.ts +1 -6
  55. package/dist/containers/Tenant/Query/QueryTabs/QueryTabs.tsx +59 -0
  56. package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.js +5 -5
  57. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.scss +55 -0
  58. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +150 -0
  59. package/dist/containers/Tenant/Query/i18n/en.json +12 -0
  60. package/dist/containers/Tenant/Query/i18n/ru.json +12 -0
  61. package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +30 -0
  62. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -1
  63. package/dist/containers/Tenant/Tenant.tsx +4 -25
  64. package/dist/containers/Tenant/TenantPages.tsx +8 -2
  65. package/dist/containers/Tenant/utils/constants.ts +10 -0
  66. package/dist/containers/Tenant/utils/schemaActions.ts +8 -3
  67. package/dist/containers/Tenants/Tenants.js +39 -37
  68. package/dist/containers/Tenants/Tenants.scss +2 -4
  69. package/dist/containers/UserSettings/i18n/en.json +2 -2
  70. package/dist/containers/UserSettings/i18n/ru.json +2 -2
  71. package/dist/containers/UserSettings/settings.ts +4 -4
  72. package/dist/containers/Versions/Versions.scss +0 -4
  73. package/dist/containers/Versions/Versions.tsx +74 -66
  74. package/dist/routes.ts +8 -6
  75. package/dist/services/api.ts +15 -7
  76. package/dist/store/reducers/clusterNodes/clusterNodes.tsx +4 -0
  77. package/dist/store/reducers/executeQuery.ts +1 -1
  78. package/dist/store/reducers/header/header.ts +31 -0
  79. package/dist/store/reducers/header/types.ts +54 -0
  80. package/dist/store/reducers/index.ts +4 -2
  81. package/dist/store/reducers/node/types.ts +2 -0
  82. package/dist/store/reducers/overview/overview.ts +109 -0
  83. package/dist/store/reducers/overview/types.ts +24 -0
  84. package/dist/store/reducers/{schema.ts → schema/schema.ts} +24 -50
  85. package/dist/{types/store/schema.ts → store/reducers/schema/types.ts} +16 -15
  86. package/dist/store/reducers/settings/settings.ts +5 -3
  87. package/dist/store/reducers/tablet.ts +18 -1
  88. package/dist/store/reducers/tenant/constants.ts +6 -0
  89. package/dist/store/reducers/tenant/tenant.ts +21 -2
  90. package/dist/store/reducers/tenant/types.ts +9 -2
  91. package/dist/store/reducers/topic.ts +1 -1
  92. package/dist/store/state-url-mapping.js +4 -1
  93. package/dist/types/api/query.ts +78 -44
  94. package/dist/types/store/explainQuery.ts +2 -2
  95. package/dist/types/store/query.ts +9 -2
  96. package/dist/types/store/tablet.ts +7 -4
  97. package/dist/utils/constants.ts +5 -1
  98. package/dist/utils/nodes.ts +1 -1
  99. package/dist/utils/query.ts +3 -3
  100. package/package.json +2 -1
  101. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.scss +0 -85
  102. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +0 -95
  103. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.js +0 -161
  104. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.scss +0 -93
  105. package/dist/containers/Tenant/QueryEditor/i18n/en.json +0 -3
  106. package/dist/containers/Tenant/QueryEditor/i18n/ru.json +0 -3
  107. package/dist/store/reducers/header.ts +0 -26
  108. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.scss +0 -0
  109. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.tsx +0 -0
  110. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/models.ts +0 -0
  111. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.scss +0 -0
  112. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.tsx +0 -0
  113. /package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.scss +0 -0
  114. /package/dist/containers/Tenant/{QueryEditor → Query}/i18n/index.ts +0 -0
@@ -1,39 +1,27 @@
1
- import {useCallback, useEffect, useMemo} from 'react';
2
- import {useDispatch} from 'react-redux';
3
- import {useLocation} from 'react-router';
4
1
  import block from 'bem-cn-lite';
5
- import qs from 'qs';
2
+
3
+ import {Skeleton} from '@gravity-ui/uikit';
6
4
 
7
5
  import EntityStatus from '../../../components/EntityStatus/EntityStatus';
8
6
  import ProgressViewer from '../../../components/ProgressViewer/ProgressViewer';
9
7
  import InfoViewer, {InfoViewerItem} from '../../../components/InfoViewer/InfoViewer';
10
8
  import {Tags} from '../../../components/Tags';
11
9
  import {Tablet} from '../../../components/Tablet';
12
- import {Loader} from '../../../components/Loader';
13
10
  import {ResponseError} from '../../../components/Errors/ResponseError';
14
11
  import {ExternalLinkWithIcon} from '../../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
12
+ import {IconWrapper as Icon} from '../../../components/Icon/Icon';
15
13
 
16
- import type {
17
- AdditionalClusterProps,
18
- AdditionalVersionsProps,
19
- ClusterLink,
20
- } from '../../../types/additionalProps';
14
+ import type {IResponseError} from '../../../types/api/error';
15
+ import type {AdditionalClusterProps, ClusterLink} from '../../../types/additionalProps';
21
16
  import type {VersionValue} from '../../../types/versions';
22
17
  import type {TClusterInfo} from '../../../types/api/cluster';
23
- import {getClusterNodes} from '../../../store/reducers/clusterNodes/clusterNodes';
24
- import {getClusterInfo} from '../../../store/reducers/cluster/cluster';
25
18
  import {backend, customBackend} from '../../../store';
26
- import {setHeader} from '../../../store/reducers/header';
27
19
  import {formatStorageValues} from '../../../utils';
28
- import {useAutofetcher, useTypedSelector} from '../../../utils/hooks';
29
- import {
30
- parseVersionsToVersionToColorMap,
31
- parseNodesToVersionsValues,
32
- } from '../../../utils/versions';
33
- import routes, {CLUSTER_PAGES, createHref} from '../../../routes';
34
-
35
- import {Versions} from '../../Versions/Versions';
20
+ import {useSetting, useTypedSelector} from '../../../utils/hooks';
21
+ import {CLUSTER_INFO_HIDDEN_KEY, DEVELOPER_UI} from '../../../utils/constants';
22
+
36
23
  import {VersionsBar} from '../VersionsBar/VersionsBar';
24
+ import {ClusterInfoSkeleton} from '../ClusterInfoSkeleton/ClusterInfoSkeleton';
37
25
 
38
26
  import {compareTablets} from './utils';
39
27
 
@@ -122,74 +110,27 @@ const getInfo = (
122
110
  };
123
111
 
124
112
  interface ClusterInfoProps {
125
- clusterTitle?: string;
113
+ cluster?: TClusterInfo;
114
+ versionsValues?: VersionValue[];
115
+ loading?: boolean;
116
+ error?: IResponseError;
126
117
  additionalClusterProps?: AdditionalClusterProps;
127
- additionalVersionsProps?: AdditionalVersionsProps;
128
118
  }
129
119
 
130
120
  export const ClusterInfo = ({
131
- clusterTitle,
121
+ cluster = {},
122
+ versionsValues = [],
123
+ loading,
124
+ error,
132
125
  additionalClusterProps = {},
133
- additionalVersionsProps = {},
134
126
  }: ClusterInfoProps) => {
135
- const dispatch = useDispatch();
136
- const location = useLocation();
137
-
138
- const queryParams = qs.parse(location.search, {
139
- ignoreQueryPrefix: true,
140
- });
141
- const {clusterName} = queryParams;
142
-
143
- const {
144
- data: cluster = {},
145
- loading,
146
- wasLoaded,
147
- error,
148
- } = useTypedSelector((state) => state.cluster);
149
- const {
150
- nodes,
151
- loading: nodesLoading,
152
- wasLoaded: nodesWasLoaded,
153
- error: nodesError,
154
- } = useTypedSelector((state) => state.clusterNodes);
155
127
  const singleClusterMode = useTypedSelector((state) => state.singleClusterMode);
156
128
 
157
- useEffect(() => {
158
- dispatch(
159
- setHeader([
160
- {
161
- text: CLUSTER_PAGES.cluster.title,
162
- link: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.cluster.id}),
163
- },
164
- ]),
165
- );
166
- }, [dispatch]);
167
-
168
- const fetchData = useCallback(() => {
169
- dispatch(getClusterInfo(clusterName ? String(clusterName) : undefined));
170
- dispatch(getClusterNodes());
171
- }, [dispatch, clusterName]);
172
-
173
- useAutofetcher(fetchData, [fetchData], true);
174
-
175
- const versionToColor = useMemo(() => {
176
- if (additionalVersionsProps?.getVersionToColorMap) {
177
- return additionalVersionsProps.getVersionToColorMap();
178
- }
179
- return parseVersionsToVersionToColorMap(cluster.Versions);
180
- }, [additionalVersionsProps, cluster]);
181
-
182
- const versionsValues = useMemo(() => {
183
- return parseNodesToVersionsValues(nodes, versionToColor);
184
- }, [nodes, versionToColor]);
185
-
186
- if ((loading && !wasLoaded) || (nodesLoading && !nodesWasLoaded)) {
187
- return <Loader size="l" />;
188
- }
129
+ const [clusterInfoHidden, setClusterInfoHidden] = useSetting<boolean>(CLUSTER_INFO_HIDDEN_KEY);
189
130
 
190
- if (error || nodesError) {
191
- return <ResponseError error={error || nodesError} />;
192
- }
131
+ const togleClusterInfoVisibility = () => {
132
+ setClusterInfoHidden(!clusterInfoHidden);
133
+ };
193
134
 
194
135
  let internalLink = backend + '/internal';
195
136
 
@@ -200,24 +141,49 @@ export const ClusterInfo = ({
200
141
  const {info = [], links = []} = additionalClusterProps;
201
142
 
202
143
  const clusterInfo = getInfo(cluster, versionsValues, info, [
203
- {title: 'Internal Viewer', url: internalLink},
144
+ {title: DEVELOPER_UI, url: internalLink},
204
145
  ...links,
205
146
  ]);
206
147
 
148
+ const getContent = () => {
149
+ if (loading) {
150
+ return <ClusterInfoSkeleton />;
151
+ }
152
+
153
+ if (error) {
154
+ <ResponseError error={error} className={b('error')} />;
155
+ }
156
+
157
+ return <InfoViewer dots={true} info={clusterInfo} />;
158
+ };
159
+
160
+ const getClusterTitle = () => {
161
+ if (loading) {
162
+ return <Skeleton className={b('title-skeleton')} />;
163
+ }
164
+
165
+ return (
166
+ <EntityStatus
167
+ size="m"
168
+ status={cluster?.Overall}
169
+ name={cluster?.Name ?? 'Unknown cluster'}
170
+ className={b('title')}
171
+ />
172
+ );
173
+ };
174
+
207
175
  return (
208
176
  <div className={b()}>
209
- <div className={b('header')}>
210
- <div className={b('title')}>
211
- <EntityStatus
212
- size="m"
213
- status={cluster.Overall}
214
- name={clusterTitle ?? cluster.Name ?? 'Unknown cluster'}
215
- />
216
- </div>
217
- <InfoViewer dots={true} info={clusterInfo} />
177
+ <div className={b('header')} onClick={togleClusterInfoVisibility}>
178
+ {getClusterTitle()}
179
+ <Icon
180
+ name="chevron-down"
181
+ width={24}
182
+ height={24}
183
+ className={b('header__expand-button', {rotate: clusterInfoHidden})}
184
+ />
218
185
  </div>
219
-
220
- <Versions nodes={nodes} versionToColor={versionToColor} />
186
+ <div className={b('info', {hidden: clusterInfoHidden})}>{getContent()}</div>
221
187
  </div>
222
188
  );
223
189
  };
@@ -0,0 +1,48 @@
1
+ .ydb-cluster-info-skeleton {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 16px;
5
+
6
+ margin-top: 5px;
7
+
8
+ &__row {
9
+ display: flex;
10
+ align-items: flex-start;
11
+
12
+ min-height: var(--yc-text-body-2-font-size);
13
+
14
+ .yc-skeleton {
15
+ min-height: var(--yc-text-body-2-font-size);
16
+ }
17
+ }
18
+
19
+ &__label {
20
+ display: flex;
21
+ flex: 0 1 auto;
22
+ align-items: baseline;
23
+
24
+ width: 200px;
25
+
26
+ &__text {
27
+ width: 100px;
28
+ }
29
+
30
+ &__dots {
31
+ width: 100px;
32
+ margin: 0 2px;
33
+
34
+ border-bottom: 1px dotted var(--yc-color-text-secondary);
35
+ }
36
+ }
37
+
38
+ &__value {
39
+ min-width: 200px;
40
+ max-width: 20%;
41
+ }
42
+
43
+ &__versions {
44
+ min-width: 400px;
45
+ max-width: 40%;
46
+ height: 36px;
47
+ }
48
+ }
@@ -0,0 +1,34 @@
1
+ import block from 'bem-cn-lite';
2
+
3
+ import {Skeleton} from '@gravity-ui/uikit';
4
+
5
+ import './ClusterInfoSkeleton.scss';
6
+
7
+ const b = block('ydb-cluster-info-skeleton');
8
+
9
+ const SkeletonLabel = () => (
10
+ <div className={b('label')}>
11
+ <Skeleton className={b('label__text')} />
12
+ <div className={b('label__dots')} />
13
+ </div>
14
+ );
15
+
16
+ interface ClusterInfoSkeletonProps {
17
+ className?: string;
18
+ rows?: number;
19
+ }
20
+
21
+ export const ClusterInfoSkeleton = ({rows = 6, className}: ClusterInfoSkeletonProps) => (
22
+ <div className={b(null, className)}>
23
+ {[...new Array(rows)].map((_, index) => (
24
+ <div className={b('row')} key={`skeleton-row-${index}`}>
25
+ <SkeletonLabel />
26
+ <Skeleton className={b('value')} />
27
+ </div>
28
+ ))}
29
+ <div className={b('row')} key="versions">
30
+ <SkeletonLabel />
31
+ <Skeleton className={b('versions')} />
32
+ </div>
33
+ </div>
34
+ );
@@ -0,0 +1,45 @@
1
+ import {Icon} from '@gravity-ui/uikit';
2
+ import cubes3Icon from '@gravity-ui/icons/svgs/cubes-3.svg';
3
+ import databasesIcon from '@gravity-ui/icons/svgs/databases.svg';
4
+ import hardDriveIcon from '@gravity-ui/icons/svgs/hard-drive.svg';
5
+
6
+ import versionsIcon from '../../assets/icons/versions.svg';
7
+
8
+ import type {ValueOf} from '../../types/common';
9
+ import routes, {createHref} from '../../routes';
10
+
11
+ export const clusterTabsIds = {
12
+ tenants: 'tenants',
13
+ nodes: 'nodes',
14
+ storage: 'storage',
15
+ versions: 'versions',
16
+ } as const;
17
+
18
+ export type ClusterTab = ValueOf<typeof clusterTabsIds>;
19
+
20
+ const tenants = {
21
+ id: clusterTabsIds.tenants,
22
+ title: 'Databases',
23
+ icon: <Icon data={databasesIcon} />,
24
+ };
25
+ const nodes = {
26
+ id: clusterTabsIds.nodes,
27
+ title: 'Nodes',
28
+ icon: <Icon data={cubes3Icon} />,
29
+ };
30
+ const storage = {
31
+ id: clusterTabsIds.storage,
32
+ title: 'Storage',
33
+ icon: <Icon data={hardDriveIcon} />,
34
+ };
35
+ const versions = {
36
+ id: clusterTabsIds.versions,
37
+ title: 'Versions',
38
+ icon: <Icon data={versionsIcon} />,
39
+ };
40
+
41
+ export const clusterTabs = [tenants, nodes, storage, versions];
42
+
43
+ export const getClusterPath = (activeTab: ClusterTab = clusterTabsIds.tenants, query = {}) => {
44
+ return createHref(routes.cluster, {activeTab}, query);
45
+ };
@@ -14,27 +14,12 @@
14
14
 
15
15
  @include body2-typography;
16
16
 
17
- &__cluster-info-title {
18
- font-size: var(--yc-text-body-1-font-size);
19
- text-transform: uppercase;
20
-
21
- color: var(--yc-color-text-secondary);
22
- }
23
-
24
- &__cluster-info-name {
25
- font-size: var(--yc-text-body-2-font-size);
26
- font-weight: 500;
27
- }
28
-
29
- &__cluster-name-wrapper {
17
+ &__breadcrumb {
30
18
  display: flex;
31
19
  align-items: center;
32
20
 
33
- height: 100%;
34
- gap: 5px;
35
- }
36
-
37
- &__divider {
38
- height: 80%;
21
+ &__icon {
22
+ margin-right: 3px;
23
+ }
39
24
  }
40
25
  }
@@ -1,66 +1,83 @@
1
- import React, {useEffect} from 'react';
1
+ import {useEffect, useMemo} from 'react';
2
+ import {useHistory, useLocation} from 'react-router';
2
3
  import {useDispatch} from 'react-redux';
3
- import cn from 'bem-cn-lite';
4
- import {useHistory} from 'react-router';
5
- import {Breadcrumbs, BreadcrumbsItem} from '@gravity-ui/uikit';
4
+ import block from 'bem-cn-lite';
5
+
6
+ import {Breadcrumbs, Icon} from '@gravity-ui/uikit';
6
7
 
7
- import Divider from '../../components/Divider/Divider';
8
8
  import {ExternalLinkWithIcon} from '../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
9
9
 
10
10
  import {backend, customBackend} from '../../store';
11
- import {getHostInfo} from '../../store/reducers/host';
12
- import {HeaderItemType} from '../../store/reducers/header';
11
+ import {getClusterInfo} from '../../store/reducers/cluster/cluster';
13
12
  import {useTypedSelector} from '../../utils/hooks';
13
+ import {DEVELOPER_UI} from '../../utils/constants';
14
+ import {parseQuery} from '../../routes';
15
+
16
+ import {RawBreadcrumbItem, getBreadcrumbs} from './breadcrumbs';
14
17
 
15
18
  import './Header.scss';
16
19
 
17
- const b = cn('header');
20
+ const b = block('header');
18
21
 
19
- function ClusterName({name}: {name: string}) {
20
- return (
21
- <div className={b('cluster-info')}>
22
- <div className={b('cluster-info-title')}>cluster</div>
23
- <div className={b('cluster-info-name')}>{name}</div>
24
- </div>
25
- );
26
- }
22
+ const getInternalLink = (singleClusterMode: boolean) => {
23
+ if (singleClusterMode && !customBackend) {
24
+ return `/internal`;
25
+ }
26
+
27
+ return backend + '/internal';
28
+ };
27
29
 
28
30
  interface HeaderProps {
29
- clusterName: string;
31
+ mainPage?: RawBreadcrumbItem;
30
32
  }
31
33
 
32
- function Header({clusterName}: HeaderProps) {
34
+ function Header({mainPage}: HeaderProps) {
33
35
  const dispatch = useDispatch();
36
+ const history = useHistory();
37
+ const location = useLocation();
34
38
 
35
- const {singleClusterMode, header}: {singleClusterMode: boolean; header: HeaderItemType[]} =
36
- useTypedSelector((state) => state);
37
- const {data: host} = useTypedSelector((state) => state.host);
39
+ const singleClusterMode = useTypedSelector((state) => state.singleClusterMode);
40
+ const {page, pageBreadcrumbsOptions} = useTypedSelector((state) => state.header);
41
+ const {data} = useTypedSelector((state) => state.cluster);
38
42
 
39
- const history = useHistory();
43
+ const queryParams = parseQuery(location);
44
+
45
+ const clusterNameFromQuery = queryParams.clusterName?.toString();
46
+
47
+ const clusterNameFinal = data?.Name || clusterNameFromQuery;
40
48
 
41
49
  useEffect(() => {
42
- dispatch(getHostInfo());
43
- }, [dispatch]);
50
+ dispatch(getClusterInfo(clusterNameFromQuery));
51
+ }, [dispatch, clusterNameFromQuery]);
44
52
 
45
- const renderHeader = () => {
46
- const clusterNameFinal = singleClusterMode ? host?.ClusterName : clusterName;
53
+ const breadcrumbItems = useMemo(() => {
54
+ const rawBreadcrumbs: RawBreadcrumbItem[] = [];
55
+ let options = pageBreadcrumbsOptions;
47
56
 
48
- let link = backend + '/internal';
57
+ if (mainPage) {
58
+ rawBreadcrumbs.push(mainPage);
59
+ }
49
60
 
50
- if (singleClusterMode && !customBackend) {
51
- link = `/internal`;
61
+ if (clusterNameFinal) {
62
+ options = {
63
+ ...pageBreadcrumbsOptions,
64
+ clusterName: clusterNameFinal,
65
+ };
52
66
  }
53
67
 
54
- const breadcrumbItems = header.reduce((acc, el) => {
68
+ const breadcrumbs = getBreadcrumbs(page, options, rawBreadcrumbs, queryParams);
69
+
70
+ return breadcrumbs.map((item) => {
55
71
  const action = () => {
56
- if (el.link) {
57
- history.push(el.link);
72
+ if (item.link) {
73
+ history.push(item.link);
58
74
  }
59
75
  };
60
- acc.push({text: el.text, action});
61
- return acc;
62
- }, [] as BreadcrumbsItem[]);
76
+ return {...item, action};
77
+ });
78
+ }, [clusterNameFinal, mainPage, history, queryParams, page, pageBreadcrumbsOptions]);
63
79
 
80
+ const renderHeader = () => {
64
81
  return (
65
82
  <header className={b()}>
66
83
  <div>
@@ -68,20 +85,29 @@ function Header({clusterName}: HeaderProps) {
68
85
  items={breadcrumbItems}
69
86
  lastDisplayedItemsCount={1}
70
87
  firstDisplayedItemsCount={1}
88
+ renderItemContent={({icon, text}) => {
89
+ if (!icon) {
90
+ return text;
91
+ }
92
+ return (
93
+ <span className={b('breadcrumb')}>
94
+ <Icon
95
+ width={16}
96
+ height={16}
97
+ data={icon}
98
+ className={b('breadcrumb__icon')}
99
+ />
100
+ {text}
101
+ </span>
102
+ );
103
+ }}
71
104
  />
72
105
  </div>
73
106
 
74
- <div className={b('cluster-name-wrapper')}>
75
- <ExternalLinkWithIcon title={'Internal Viewer'} url={link} />
76
- {clusterNameFinal && (
77
- <React.Fragment>
78
- <div className={b('divider')}>
79
- <Divider />
80
- </div>
81
- <ClusterName name={clusterNameFinal} />
82
- </React.Fragment>
83
- )}
84
- </div>
107
+ <ExternalLinkWithIcon
108
+ title={DEVELOPER_UI}
109
+ url={getInternalLink(singleClusterMode)}
110
+ />
85
111
  </header>
86
112
  );
87
113
  };
@@ -0,0 +1,146 @@
1
+ import nodesRightIcon from '@gravity-ui/icons/svgs/nodes-right.svg';
2
+ import databaseIcon from '@gravity-ui/icons/svgs/database.svg';
3
+
4
+ import type {
5
+ BreadcrumbsOptions,
6
+ ClusterBreadcrumbsOptions,
7
+ NodeBreadcrumbsOptions,
8
+ Page,
9
+ TabletBreadcrumbsOptions,
10
+ TabletsBreadcrumbsOptions,
11
+ TenantBreadcrumbsOptions,
12
+ } from '../../store/reducers/header/types';
13
+ import routes, {createHref} from '../../routes';
14
+
15
+ import {getClusterPath} from '../Cluster/utils';
16
+ import {getTenantPath} from '../Tenant/TenantPages';
17
+ import {getDefaultNodePath} from '../Node/NodePages';
18
+
19
+ const prepareTenantName = (tenantName: string) => {
20
+ return tenantName.startsWith('/') ? tenantName.slice(1) : tenantName;
21
+ };
22
+
23
+ export interface RawBreadcrumbItem {
24
+ text: string;
25
+ link?: string;
26
+ icon?: SVGIconData;
27
+ }
28
+
29
+ const getClusterBreadcrumbs = (
30
+ options: ClusterBreadcrumbsOptions,
31
+ query = {},
32
+ ): RawBreadcrumbItem[] => {
33
+ const {clusterName, clusterTab} = options;
34
+
35
+ return [
36
+ {
37
+ text: clusterName || 'Cluster',
38
+ link: getClusterPath(clusterTab, query),
39
+ icon: nodesRightIcon,
40
+ },
41
+ ];
42
+ };
43
+
44
+ const getTenantBreadcrumbs = (
45
+ options: TenantBreadcrumbsOptions,
46
+ query = {},
47
+ ): RawBreadcrumbItem[] => {
48
+ const {tenantName} = options;
49
+
50
+ const text = tenantName ? prepareTenantName(tenantName) : 'Tenant';
51
+ const link = tenantName ? getTenantPath({...query, name: tenantName}) : undefined;
52
+
53
+ return [...getClusterBreadcrumbs(options, query), {text, link, icon: databaseIcon}];
54
+ };
55
+
56
+ const getNodeBreadcrumbs = (options: NodeBreadcrumbsOptions, query = {}): RawBreadcrumbItem[] => {
57
+ const {tenantName, nodeId} = options;
58
+
59
+ let breadcrumbs: RawBreadcrumbItem[];
60
+
61
+ // Compute nodes have tenantName, storage nodes doesn't
62
+ const isStorageNode = !tenantName;
63
+
64
+ if (isStorageNode) {
65
+ breadcrumbs = getClusterBreadcrumbs(options, query);
66
+ } else {
67
+ breadcrumbs = getTenantBreadcrumbs(options, query);
68
+ }
69
+
70
+ const text = nodeId ? `Node ${nodeId}` : 'Node';
71
+ const link = nodeId ? getDefaultNodePath(nodeId, query) : undefined;
72
+
73
+ breadcrumbs.push({text, link});
74
+
75
+ return breadcrumbs;
76
+ };
77
+
78
+ const getTabletsBreadcrubms = (
79
+ options: TabletsBreadcrumbsOptions,
80
+ query = {},
81
+ ): RawBreadcrumbItem[] => {
82
+ const {tenantName, nodeIds, state, type} = options;
83
+
84
+ let breadcrumbs: RawBreadcrumbItem[];
85
+
86
+ // Cluster system tablets don't have tenantName
87
+ if (tenantName) {
88
+ breadcrumbs = getTenantBreadcrumbs(options, query);
89
+ } else {
90
+ breadcrumbs = getClusterBreadcrumbs(options, query);
91
+ }
92
+
93
+ const link = createHref(routes.tabletsFilters, undefined, {
94
+ nodeIds,
95
+ state,
96
+ type,
97
+ path: tenantName,
98
+ });
99
+
100
+ breadcrumbs.push({text: 'Tablets', link});
101
+
102
+ return breadcrumbs;
103
+ };
104
+
105
+ const getTabletBreadcrubms = (
106
+ options: TabletBreadcrumbsOptions,
107
+ query = {},
108
+ ): RawBreadcrumbItem[] => {
109
+ const {tabletId} = options;
110
+
111
+ const breadcrumbs = getTabletsBreadcrubms(options, query);
112
+
113
+ breadcrumbs.push({
114
+ text: tabletId || 'Tablet',
115
+ });
116
+
117
+ return breadcrumbs;
118
+ };
119
+
120
+ export const getBreadcrumbs = (
121
+ page: Page,
122
+ options: BreadcrumbsOptions,
123
+ rawBreadcrumbs: RawBreadcrumbItem[] = [],
124
+ query = {},
125
+ ) => {
126
+ switch (page) {
127
+ case 'cluster': {
128
+ return [...rawBreadcrumbs, ...getClusterBreadcrumbs(options, query)];
129
+ }
130
+ case 'tenant': {
131
+ return [...rawBreadcrumbs, ...getTenantBreadcrumbs(options, query)];
132
+ }
133
+ case 'node': {
134
+ return [...rawBreadcrumbs, ...getNodeBreadcrumbs(options, query)];
135
+ }
136
+ case 'tablets': {
137
+ return [...rawBreadcrumbs, ...getTabletsBreadcrubms(options, query)];
138
+ }
139
+ case 'tablet': {
140
+ return [...rawBreadcrumbs, ...getTabletBreadcrubms(options, query)];
141
+ }
142
+ default: {
143
+ return rawBreadcrumbs;
144
+ }
145
+ }
146
+ };