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.
- package/CHANGELOG.md +33 -0
- package/dist/assets/icons/versions.svg +3 -0
- package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +7 -2
- package/dist/components/Tablet/Tablet.tsx +17 -3
- package/dist/components/TabletsStatistic/TabletsStatistic.tsx +23 -16
- package/dist/containers/App/Content.js +8 -4
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +4 -50
- package/dist/containers/Cluster/Cluster.scss +7 -48
- package/dist/containers/Cluster/Cluster.tsx +129 -20
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +34 -17
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +58 -92
- package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.scss +48 -0
- package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +34 -0
- package/dist/containers/Cluster/utils.tsx +45 -0
- package/dist/containers/Header/Header.scss +4 -19
- package/dist/containers/Header/Header.tsx +72 -46
- package/dist/containers/Header/breadcrumbs.ts +146 -0
- package/dist/containers/Node/Node.tsx +25 -29
- package/dist/containers/Node/NodePages.ts +10 -6
- package/dist/containers/Nodes/Nodes.tsx +0 -16
- package/dist/containers/Nodes/getNodesColumns.tsx +1 -1
- package/dist/containers/Storage/Storage.js +1 -11
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +11 -3
- package/dist/containers/Tablet/Tablet.tsx +40 -4
- package/dist/containers/Tablet/TabletInfo/TabletInfo.tsx +2 -2
- package/dist/containers/TabletsFilters/TabletsFilters.js +15 -2
- package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +1 -1
- package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +1 -1
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +7 -0
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -4
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +5 -3
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +4 -6
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +56 -53
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +2 -1
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +11 -13
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +1 -1
- package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +2 -2
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +7 -3
- package/dist/containers/Tenant/Preview/Preview.js +1 -1
- package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.js → Query/ExecuteResult/ExecuteResult.js} +3 -5
- package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.scss → Query/ExecuteResult/ExecuteResult.scss} +1 -1
- package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.js → Query/ExplainResult/ExplainResult.js} +3 -5
- package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.scss → Query/ExplainResult/ExplainResult.scss} +1 -1
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +20 -0
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +60 -0
- package/dist/containers/Tenant/Query/Query.scss +16 -0
- package/dist/containers/Tenant/Query/Query.tsx +73 -0
- package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.js +43 -100
- package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.scss +7 -23
- package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/OldQueryEditorControls.tsx +10 -3
- package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.scss +1 -4
- package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.tsx +8 -1
- package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/shared.ts +1 -6
- package/dist/containers/Tenant/Query/QueryTabs/QueryTabs.tsx +59 -0
- package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.js +5 -5
- package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.scss +55 -0
- package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +150 -0
- package/dist/containers/Tenant/Query/i18n/en.json +12 -0
- package/dist/containers/Tenant/Query/i18n/ru.json +12 -0
- package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +30 -0
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -1
- package/dist/containers/Tenant/Tenant.tsx +4 -25
- package/dist/containers/Tenant/TenantPages.tsx +8 -2
- package/dist/containers/Tenant/utils/constants.ts +10 -0
- package/dist/containers/Tenant/utils/schemaActions.ts +8 -3
- package/dist/containers/Tenants/Tenants.js +39 -37
- package/dist/containers/Tenants/Tenants.scss +2 -4
- package/dist/containers/UserSettings/i18n/en.json +2 -2
- package/dist/containers/UserSettings/i18n/ru.json +2 -2
- package/dist/containers/UserSettings/settings.ts +4 -4
- package/dist/containers/Versions/Versions.scss +0 -4
- package/dist/containers/Versions/Versions.tsx +74 -66
- package/dist/routes.ts +8 -6
- package/dist/services/api.ts +15 -7
- package/dist/store/reducers/clusterNodes/clusterNodes.tsx +4 -0
- package/dist/store/reducers/executeQuery.ts +1 -1
- package/dist/store/reducers/header/header.ts +31 -0
- package/dist/store/reducers/header/types.ts +54 -0
- package/dist/store/reducers/index.ts +4 -2
- package/dist/store/reducers/node/types.ts +2 -0
- package/dist/store/reducers/overview/overview.ts +109 -0
- package/dist/store/reducers/overview/types.ts +24 -0
- package/dist/store/reducers/{schema.ts → schema/schema.ts} +24 -50
- package/dist/{types/store/schema.ts → store/reducers/schema/types.ts} +16 -15
- package/dist/store/reducers/settings/settings.ts +5 -3
- package/dist/store/reducers/tablet.ts +18 -1
- package/dist/store/reducers/tenant/constants.ts +6 -0
- package/dist/store/reducers/tenant/tenant.ts +21 -2
- package/dist/store/reducers/tenant/types.ts +9 -2
- package/dist/store/reducers/topic.ts +1 -1
- package/dist/store/state-url-mapping.js +4 -1
- package/dist/types/api/query.ts +78 -44
- package/dist/types/store/explainQuery.ts +2 -2
- package/dist/types/store/query.ts +9 -2
- package/dist/types/store/tablet.ts +7 -4
- package/dist/utils/constants.ts +5 -1
- package/dist/utils/nodes.ts +1 -1
- package/dist/utils/query.ts +3 -3
- package/package.json +2 -1
- package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.scss +0 -85
- package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +0 -95
- package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.js +0 -161
- package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.scss +0 -93
- package/dist/containers/Tenant/QueryEditor/i18n/en.json +0 -3
- package/dist/containers/Tenant/QueryEditor/i18n/ru.json +0 -3
- package/dist/store/reducers/header.ts +0 -26
- /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.scss +0 -0
- /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.tsx +0 -0
- /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/models.ts +0 -0
- /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.scss +0 -0
- /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.tsx +0 -0
- /package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.scss +0 -0
- /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
|
-
|
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
|
-
|
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 {
|
29
|
-
import {
|
30
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
191
|
-
|
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:
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
-
&
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
&__divider {
|
38
|
-
height: 80%;
|
21
|
+
&__icon {
|
22
|
+
margin-right: 3px;
|
23
|
+
}
|
39
24
|
}
|
40
25
|
}
|
@@ -1,66 +1,83 @@
|
|
1
|
-
import
|
1
|
+
import {useEffect, useMemo} from 'react';
|
2
|
+
import {useHistory, useLocation} from 'react-router';
|
2
3
|
import {useDispatch} from 'react-redux';
|
3
|
-
import
|
4
|
-
|
5
|
-
import {Breadcrumbs,
|
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 {
|
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 =
|
20
|
+
const b = block('header');
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
31
|
+
mainPage?: RawBreadcrumbItem;
|
30
32
|
}
|
31
33
|
|
32
|
-
function Header({
|
34
|
+
function Header({mainPage}: HeaderProps) {
|
33
35
|
const dispatch = useDispatch();
|
36
|
+
const history = useHistory();
|
37
|
+
const location = useLocation();
|
34
38
|
|
35
|
-
const
|
36
|
-
|
37
|
-
const {data
|
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
|
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(
|
43
|
-
}, [dispatch]);
|
50
|
+
dispatch(getClusterInfo(clusterNameFromQuery));
|
51
|
+
}, [dispatch, clusterNameFromQuery]);
|
44
52
|
|
45
|
-
const
|
46
|
-
const
|
53
|
+
const breadcrumbItems = useMemo(() => {
|
54
|
+
const rawBreadcrumbs: RawBreadcrumbItem[] = [];
|
55
|
+
let options = pageBreadcrumbsOptions;
|
47
56
|
|
48
|
-
|
57
|
+
if (mainPage) {
|
58
|
+
rawBreadcrumbs.push(mainPage);
|
59
|
+
}
|
49
60
|
|
50
|
-
if (
|
51
|
-
|
61
|
+
if (clusterNameFinal) {
|
62
|
+
options = {
|
63
|
+
...pageBreadcrumbsOptions,
|
64
|
+
clusterName: clusterNameFinal,
|
65
|
+
};
|
52
66
|
}
|
53
67
|
|
54
|
-
const
|
68
|
+
const breadcrumbs = getBreadcrumbs(page, options, rawBreadcrumbs, queryParams);
|
69
|
+
|
70
|
+
return breadcrumbs.map((item) => {
|
55
71
|
const action = () => {
|
56
|
-
if (
|
57
|
-
history.push(
|
72
|
+
if (item.link) {
|
73
|
+
history.push(item.link);
|
58
74
|
}
|
59
75
|
};
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
<
|
75
|
-
|
76
|
-
{
|
77
|
-
|
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
|
+
};
|