ydb-embedded-ui 4.5.2 → 4.6.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.
- package/CHANGELOG.md +13 -0
- package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +1 -1
- package/dist/containers/App/Content.js +3 -2
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +4 -50
- package/dist/containers/Cluster/Cluster.scss +7 -48
- package/dist/containers/Cluster/Cluster.tsx +136 -20
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +34 -17
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +57 -91
- package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.scss +48 -0
- package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +34 -0
- package/dist/containers/Cluster/utils.ts +34 -0
- package/dist/containers/Header/Header.scss +0 -24
- package/dist/containers/Header/Header.tsx +14 -44
- package/dist/containers/Node/Node.tsx +22 -20
- 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/Tablet/Tablet.tsx +28 -0
- package/dist/containers/TabletsFilters/TabletsFilters.js +16 -1
- package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +1 -1
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +3 -0
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -3
- 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/TopShards/TopShards.tsx +1 -1
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +1 -1
- package/dist/containers/Tenant/Preview/Preview.js +1 -1
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +26 -22
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +10 -3
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +8 -1
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/shared.ts +1 -6
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -1
- package/dist/containers/Tenant/Tenant.tsx +7 -12
- package/dist/containers/Tenant/utils/schemaActions.ts +1 -1
- package/dist/containers/Tenants/Tenants.js +18 -28
- 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 +0 -7
- package/dist/services/api.ts +6 -2
- package/dist/store/reducers/clusterNodes/clusterNodes.tsx +4 -0
- package/dist/store/reducers/index.ts +3 -1
- 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/types/api/query.ts +78 -44
- package/dist/types/store/explainQuery.ts +2 -2
- package/dist/types/store/query.ts +4 -2
- package/dist/utils/constants.ts +3 -1
- package/dist/utils/nodes.ts +1 -1
- package/dist/utils/query.ts +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [4.6.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.5.2...v4.6.0) (2023-06-13)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **QueryEditor:** add data and query modes ([#422](https://github.com/ydb-platform/ydb-embedded-ui/issues/422)) ([c142f03](https://github.com/ydb-platform/ydb-embedded-ui/commit/c142f03e9caeab4dcf1d34b3988e949a94213932))
|
9
|
+
* rework navigation, update breadcrumbs ([#418](https://github.com/ydb-platform/ydb-embedded-ui/issues/418)) ([2d807d6](https://github.com/ydb-platform/ydb-embedded-ui/commit/2d807d6a52e13edcf2a7e1591672224339d91949))
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* **Diagnostics:** remove unneded tenantInfo fetch ([#420](https://github.com/ydb-platform/ydb-embedded-ui/issues/420)) ([ccaafe4](https://github.com/ydb-platform/ydb-embedded-ui/commit/ccaafe4ec9346ee1ec2ebd2a62600274f2175bfb))
|
15
|
+
|
3
16
|
## [4.5.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.5.1...v4.5.2) (2023-06-06)
|
4
17
|
|
5
18
|
|
@@ -16,7 +16,7 @@ const b = block('ydb-node-host-wrapper');
|
|
16
16
|
|
17
17
|
interface NodeHostWrapperProps {
|
18
18
|
node: INodesPreparedEntity;
|
19
|
-
getNodeRef?: (node?: NodeAddress) => string;
|
19
|
+
getNodeRef?: (node?: NodeAddress) => string | null;
|
20
20
|
}
|
21
21
|
|
22
22
|
export const NodeHostWrapper = ({node, getNodeRef}: NodeHostWrapperProps) => {
|
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
|
|
5
5
|
|
6
6
|
import {ThemeProvider} from '@gravity-ui/uikit';
|
7
7
|
|
8
|
-
import routes, {createHref
|
8
|
+
import routes, {createHref} from '../../routes';
|
9
9
|
|
10
10
|
import Cluster from '../Cluster/Cluster';
|
11
11
|
import Tenant from '../Tenant/Tenant';
|
@@ -23,6 +23,7 @@ import './App.scss';
|
|
23
23
|
import PropTypes from 'prop-types';
|
24
24
|
import HistoryContext from '../../contexts/HistoryContext';
|
25
25
|
import Authentication from '../Authentication/Authentication';
|
26
|
+
import {clusterTabsIds} from '../Cluster/utils';
|
26
27
|
|
27
28
|
const b = cn('app');
|
28
29
|
|
@@ -44,7 +45,7 @@ export function Content(props) {
|
|
44
45
|
<Route path={routes.tabletsFilters} component={TabletsFilters} />
|
45
46
|
<Redirect
|
46
47
|
to={createHref(routes.cluster, {
|
47
|
-
activeTab:
|
48
|
+
activeTab: clusterTabsIds.tenants,
|
48
49
|
})}
|
49
50
|
/>
|
50
51
|
</Switch>
|
@@ -9,11 +9,7 @@ import {AsideHeader, MenuItem as AsideHeaderMenuItem, FooterItem} from '@gravity
|
|
9
9
|
|
10
10
|
import signOutIcon from '../../assets/icons/signOut.svg';
|
11
11
|
import signInIcon from '../../assets/icons/signIn.svg';
|
12
|
-
import databaseIcon from '../../assets/icons/server.svg';
|
13
|
-
import storageIcon from '../../assets/icons/storage.svg';
|
14
|
-
import clusterIcon from '../../assets/icons/cluster.svg';
|
15
12
|
import ydbLogoIcon from '../../assets/icons/ydb.svg';
|
16
|
-
import databasesIcon from '../../assets/icons/databases.svg';
|
17
13
|
import userSecret from '../../assets/icons/user-secret.svg';
|
18
14
|
import userChecked from '../../assets/icons/user-check.svg';
|
19
15
|
import settingsIcon from '../../assets/icons/settings.svg';
|
@@ -21,7 +17,7 @@ import supportIcon from '../../assets/icons/support.svg';
|
|
21
17
|
|
22
18
|
import {UserSettings} from '../UserSettings/UserSettings';
|
23
19
|
|
24
|
-
import routes, {createHref
|
20
|
+
import routes, {createHref} from '../../routes';
|
25
21
|
|
26
22
|
import {logout} from '../../store/reducers/authentication';
|
27
23
|
import {getParsedSettingValue, setSettingValue} from '../../store/reducers/settings/settings';
|
@@ -115,46 +111,8 @@ interface AsideNavigationProps {
|
|
115
111
|
setSettingValue: (name: string, value: string) => void;
|
116
112
|
}
|
117
113
|
|
118
|
-
|
119
|
-
|
120
|
-
id: CLUSTER_PAGES.tenants.id,
|
121
|
-
title: 'Databases',
|
122
|
-
icon: databasesIcon,
|
123
|
-
iconSize: 20,
|
124
|
-
location: createHref(routes.cluster, {
|
125
|
-
activeTab: CLUSTER_PAGES.tenants.id,
|
126
|
-
}),
|
127
|
-
locationKeys: ['/tenant'],
|
128
|
-
},
|
129
|
-
{
|
130
|
-
id: CLUSTER_PAGES.nodes.id,
|
131
|
-
title: 'Nodes',
|
132
|
-
icon: databaseIcon,
|
133
|
-
iconSize: 20,
|
134
|
-
location: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.nodes.id}),
|
135
|
-
locationKeys: ['/node'],
|
136
|
-
},
|
137
|
-
{
|
138
|
-
id: CLUSTER_PAGES.storage.id,
|
139
|
-
title: 'Storage',
|
140
|
-
icon: storageIcon,
|
141
|
-
iconSize: 20,
|
142
|
-
location: createHref(routes.cluster, {
|
143
|
-
activeTab: CLUSTER_PAGES.storage.id,
|
144
|
-
}),
|
145
|
-
locationKeys: ['/storage'],
|
146
|
-
},
|
147
|
-
{
|
148
|
-
id: CLUSTER_PAGES.cluster.id,
|
149
|
-
title: 'Cluster',
|
150
|
-
icon: clusterIcon,
|
151
|
-
iconSize: 20,
|
152
|
-
location: createHref(routes.cluster, {
|
153
|
-
activeTab: CLUSTER_PAGES.cluster.id,
|
154
|
-
}),
|
155
|
-
locationKeys: ['/cluster/cluster'],
|
156
|
-
},
|
157
|
-
];
|
114
|
+
// FIXME: add items or delete
|
115
|
+
const items: MenuItem[] = [];
|
158
116
|
|
159
117
|
enum Panel {
|
160
118
|
UserSettings = 'UserSettings',
|
@@ -172,17 +130,13 @@ function AsideNavigation(props: AsideNavigationProps) {
|
|
172
130
|
|
173
131
|
const menuItems: AsideHeaderMenuItem[] = React.useMemo(() => {
|
174
132
|
const {pathname} = location;
|
175
|
-
const isClusterPage = pathname === '/cluster';
|
176
133
|
const menuItems: AsideHeaderMenuItem[] = items.map((item) => {
|
177
134
|
const locationKeysCoincidence = item.locationKeys?.filter((key) =>
|
178
135
|
pathname.startsWith(key),
|
179
136
|
);
|
180
|
-
|
137
|
+
const current =
|
181
138
|
(locationKeysCoincidence && locationKeysCoincidence.length > 0) ||
|
182
139
|
item.location.startsWith(pathname);
|
183
|
-
if (isClusterPage && item.id !== CLUSTER_PAGES.tenants.id) {
|
184
|
-
current = false;
|
185
|
-
}
|
186
140
|
return {
|
187
141
|
id: item.id,
|
188
142
|
title: item.title,
|
@@ -2,57 +2,16 @@
|
|
2
2
|
|
3
3
|
.cluster {
|
4
4
|
overflow: auto;
|
5
|
+
flex-grow: 1;
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
&_tab_cluster {
|
10
|
-
padding: 0px;
|
11
|
-
}
|
12
|
-
|
13
|
-
&__tab {
|
14
|
-
text-decoration: none;
|
15
|
-
|
16
|
-
&:first-letter {
|
17
|
-
text-transform: uppercase;
|
18
|
-
}
|
19
|
-
}
|
20
|
-
|
21
|
-
&__format-label {
|
22
|
-
margin-right: 15px;
|
23
|
-
}
|
24
|
-
|
25
|
-
&__title {
|
26
|
-
text-align: center;
|
27
|
-
}
|
7
|
+
height: 100%;
|
8
|
+
padding: 20px 20px 0px;
|
28
9
|
|
29
|
-
|
30
|
-
animation: none !important;
|
31
|
-
}
|
32
|
-
|
33
|
-
&__search {
|
34
|
-
width: 255px;
|
35
|
-
margin: 0 15px 0 0;
|
36
|
-
}
|
37
|
-
|
38
|
-
&__tablets {
|
39
|
-
padding: 0 !important;
|
40
|
-
|
41
|
-
.tablets-viewer__grid {
|
42
|
-
grid-template-columns: 125px;
|
43
|
-
}
|
44
|
-
}
|
45
|
-
|
46
|
-
&__controls {
|
47
|
-
display: flex;
|
48
|
-
justify-content: space-between;
|
10
|
+
@include flex-container();
|
49
11
|
|
50
|
-
|
51
|
-
|
12
|
+
&__content {
|
13
|
+
overflow: auto;
|
52
14
|
|
53
|
-
|
54
|
-
display: flex;
|
55
|
-
flex: 1 1 auto;
|
56
|
-
flex-direction: column;
|
15
|
+
height: 100%;
|
57
16
|
}
|
58
17
|
}
|
@@ -1,51 +1,133 @@
|
|
1
|
-
import {
|
1
|
+
import {useEffect, useMemo} from 'react';
|
2
|
+
import {useLocation, useRouteMatch} from 'react-router';
|
3
|
+
import {useDispatch} from 'react-redux';
|
2
4
|
import cn from 'bem-cn-lite';
|
5
|
+
import qs from 'qs';
|
6
|
+
|
7
|
+
import {Tabs} from '@gravity-ui/uikit';
|
3
8
|
|
4
9
|
import type {AdditionalClusterProps, AdditionalVersionsProps} from '../../types/additionalProps';
|
5
|
-
import
|
10
|
+
import type {AdditionalNodesInfo} from '../../utils/nodes';
|
11
|
+
import routes from '../../routes';
|
6
12
|
|
7
|
-
import {
|
13
|
+
import {setHeader} from '../../store/reducers/header';
|
14
|
+
import {getClusterInfo} from '../../store/reducers/cluster/cluster';
|
15
|
+
import {getClusterNodes} from '../../store/reducers/clusterNodes/clusterNodes';
|
16
|
+
import {parseNodesToVersionsValues, parseVersionsToVersionToColorMap} from '../../utils/versions';
|
17
|
+
import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
|
18
|
+
|
19
|
+
import {InternalLink} from '../../components/InternalLink';
|
8
20
|
import Tenants from '../Tenants/Tenants';
|
9
21
|
import {Nodes} from '../Nodes/Nodes';
|
10
22
|
import Storage from '../Storage/Storage';
|
23
|
+
import {Versions} from '../Versions/Versions';
|
24
|
+
|
25
|
+
import {ClusterInfo} from './ClusterInfo/ClusterInfo';
|
26
|
+
import {ClusterTab, clusterTabs, clusterTabsIds, getClusterPath} from './utils';
|
11
27
|
|
12
28
|
import './Cluster.scss';
|
13
29
|
|
14
30
|
const b = cn('cluster');
|
15
31
|
|
16
32
|
interface ClusterProps {
|
17
|
-
additionalTenantsInfo?:
|
18
|
-
additionalNodesInfo?:
|
33
|
+
additionalTenantsInfo?: unknown;
|
34
|
+
additionalNodesInfo?: AdditionalNodesInfo;
|
19
35
|
additionalClusterProps?: AdditionalClusterProps;
|
20
36
|
additionalVersionsProps?: AdditionalVersionsProps;
|
21
37
|
}
|
22
38
|
|
23
39
|
function Cluster({
|
40
|
+
additionalClusterProps,
|
24
41
|
additionalTenantsInfo,
|
25
42
|
additionalNodesInfo,
|
26
|
-
additionalClusterProps,
|
27
43
|
additionalVersionsProps,
|
28
44
|
}: ClusterProps) {
|
29
|
-
const
|
30
|
-
|
31
|
-
const
|
45
|
+
const dispatch = useDispatch();
|
46
|
+
|
47
|
+
const match = useRouteMatch<{activeTab: string}>(routes.cluster);
|
48
|
+
const {activeTab = clusterTabsIds.tenants} = match?.params || {};
|
49
|
+
|
50
|
+
const location = useLocation();
|
51
|
+
const queryParams = qs.parse(location.search, {
|
52
|
+
ignoreQueryPrefix: true,
|
53
|
+
});
|
54
|
+
const {clusterName} = queryParams;
|
55
|
+
|
56
|
+
const {
|
57
|
+
data: cluster = {},
|
58
|
+
loading: clusterLoading,
|
59
|
+
wasLoaded: clusterWasLoaded,
|
60
|
+
error: clusterError,
|
61
|
+
} = useTypedSelector((state) => state.cluster);
|
62
|
+
const {
|
63
|
+
nodes,
|
64
|
+
loading: nodesLoading,
|
65
|
+
wasLoaded: nodesWasLoaded,
|
66
|
+
} = useTypedSelector((state) => state.clusterNodes);
|
67
|
+
|
68
|
+
const {Name} = cluster;
|
69
|
+
|
70
|
+
const infoLoading = (clusterLoading && !clusterWasLoaded) || (nodesLoading && !nodesWasLoaded);
|
71
|
+
|
72
|
+
useEffect(() => {
|
73
|
+
dispatch(getClusterNodes());
|
74
|
+
}, [dispatch]);
|
75
|
+
|
76
|
+
useAutofetcher(
|
77
|
+
() => dispatch(getClusterInfo(clusterName ? String(clusterName) : undefined)),
|
78
|
+
[dispatch, clusterName],
|
79
|
+
true,
|
80
|
+
);
|
81
|
+
|
82
|
+
useEffect(() => {
|
83
|
+
dispatch(
|
84
|
+
setHeader([
|
85
|
+
{
|
86
|
+
text: Name || 'Cluster',
|
87
|
+
link: getClusterPath(),
|
88
|
+
},
|
89
|
+
]),
|
90
|
+
);
|
91
|
+
}, [dispatch, Name]);
|
92
|
+
|
93
|
+
const versionToColor = useMemo(() => {
|
94
|
+
if (additionalVersionsProps?.getVersionToColorMap) {
|
95
|
+
return additionalVersionsProps?.getVersionToColorMap();
|
96
|
+
}
|
97
|
+
return parseVersionsToVersionToColorMap(cluster?.Versions);
|
98
|
+
}, [additionalVersionsProps, cluster]);
|
99
|
+
|
100
|
+
const versionsValues = useMemo(() => {
|
101
|
+
return parseNodesToVersionsValues(nodes, versionToColor);
|
102
|
+
}, [nodes, versionToColor]);
|
103
|
+
|
104
|
+
const renderTab = () => {
|
32
105
|
switch (activeTab) {
|
33
|
-
case
|
106
|
+
case clusterTabsIds.tenants: {
|
34
107
|
return <Tenants additionalTenantsInfo={additionalTenantsInfo} />;
|
35
108
|
}
|
36
|
-
case
|
109
|
+
case clusterTabsIds.nodes: {
|
37
110
|
return <Nodes additionalNodesInfo={additionalNodesInfo} />;
|
38
111
|
}
|
39
|
-
case
|
112
|
+
case clusterTabsIds.storage: {
|
40
113
|
return <Storage additionalNodesInfo={additionalNodesInfo} />;
|
41
114
|
}
|
42
|
-
case
|
43
|
-
return
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
115
|
+
case clusterTabsIds.versions: {
|
116
|
+
return <Versions versionToColor={versionToColor} />;
|
117
|
+
}
|
118
|
+
default: {
|
119
|
+
return null;
|
120
|
+
}
|
121
|
+
}
|
122
|
+
};
|
123
|
+
|
124
|
+
const getTabEntityCount = (tabId: ClusterTab) => {
|
125
|
+
switch (tabId) {
|
126
|
+
case clusterTabsIds.tenants: {
|
127
|
+
return cluster?.Tenants;
|
128
|
+
}
|
129
|
+
case clusterTabsIds.nodes: {
|
130
|
+
return cluster?.NodesTotal;
|
49
131
|
}
|
50
132
|
default: {
|
51
133
|
return null;
|
@@ -53,7 +135,41 @@ function Cluster({
|
|
53
135
|
}
|
54
136
|
};
|
55
137
|
|
56
|
-
return
|
138
|
+
return (
|
139
|
+
<div className={b()}>
|
140
|
+
<ClusterInfo
|
141
|
+
cluster={cluster}
|
142
|
+
versionsValues={versionsValues}
|
143
|
+
loading={infoLoading}
|
144
|
+
error={clusterError}
|
145
|
+
additionalClusterProps={additionalClusterProps}
|
146
|
+
/>
|
147
|
+
|
148
|
+
<div>
|
149
|
+
<Tabs
|
150
|
+
size="l"
|
151
|
+
allowNotSelected={true}
|
152
|
+
activeTab={activeTab as string}
|
153
|
+
items={clusterTabs.map((item) => {
|
154
|
+
return {
|
155
|
+
...item,
|
156
|
+
title: `${item.title} ${getTabEntityCount(item.id) || ''}`,
|
157
|
+
};
|
158
|
+
})}
|
159
|
+
wrapTo={({id}, node) => {
|
160
|
+
const path = getClusterPath(id as ClusterTab, queryParams);
|
161
|
+
return (
|
162
|
+
<InternalLink to={path} key={id}>
|
163
|
+
{node}
|
164
|
+
</InternalLink>
|
165
|
+
);
|
166
|
+
}}
|
167
|
+
/>
|
168
|
+
</div>
|
169
|
+
|
170
|
+
<div className={b('content')}>{renderTab()}</div>
|
171
|
+
</div>
|
172
|
+
);
|
57
173
|
}
|
58
174
|
|
59
175
|
export default Cluster;
|
@@ -1,31 +1,48 @@
|
|
1
1
|
@import '../../../styles/mixins';
|
2
2
|
|
3
3
|
.cluster-info {
|
4
|
-
|
5
|
-
$progress_width: 100%;
|
4
|
+
width: 100%;
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
flex-direction: column;
|
6
|
+
&__header {
|
7
|
+
display: flex;
|
10
8
|
|
11
|
-
|
9
|
+
width: fit-content;
|
10
|
+
margin-bottom: 20px;
|
12
11
|
|
13
|
-
|
14
|
-
line-height: var(--yc-text-body-2-line-height);
|
12
|
+
cursor: pointer;
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
&__expand-button {
|
15
|
+
margin-left: 6px;
|
18
16
|
|
19
|
-
|
20
|
-
|
17
|
+
&_rotate {
|
18
|
+
transform: rotate(180deg);
|
19
|
+
}
|
20
|
+
}
|
21
21
|
}
|
22
22
|
|
23
|
-
&__title {
|
24
|
-
|
25
|
-
|
23
|
+
&__title .entity-status__name {
|
24
|
+
font-size: var(--yc-text-header-1-font-size);
|
25
|
+
font-weight: var(--yc-text-header-font-weight);
|
26
|
+
line-height: var(--yc-text-header-1-line-height);
|
27
|
+
}
|
28
|
+
|
29
|
+
&__title-skeleton {
|
30
|
+
width: 20%;
|
31
|
+
min-width: 200px;
|
32
|
+
height: var(--yc-text-header-1-line-height);
|
33
|
+
}
|
26
34
|
|
27
|
-
|
35
|
+
&__error {
|
36
|
+
font-size: var(--yc-text-body-2-font-size);
|
37
|
+
line-height: var(--yc-text-body-2-line-height);
|
38
|
+
}
|
39
|
+
|
40
|
+
&__info {
|
28
41
|
margin-bottom: 20px;
|
42
|
+
|
43
|
+
&_hidden {
|
44
|
+
display: none;
|
45
|
+
}
|
29
46
|
}
|
30
47
|
|
31
48
|
&__metric-field {
|
@@ -38,7 +55,7 @@
|
|
38
55
|
align-items: center;
|
39
56
|
|
40
57
|
& .tablet {
|
41
|
-
margin-
|
58
|
+
margin-top: 2px;
|
42
59
|
}
|
43
60
|
}
|
44
61
|
|
@@ -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} 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(CLUSTER_INFO_HIDDEN_KEY, false);
|
189
130
|
|
190
|
-
|
191
|
-
|
192
|
-
}
|
131
|
+
const togleClusterInfoVisibility = () => {
|
132
|
+
setClusterInfoHidden(!clusterInfoHidden);
|
133
|
+
};
|
193
134
|
|
194
135
|
let internalLink = backend + '/internal';
|
195
136
|
|
@@ -204,20 +145,45 @@ export const ClusterInfo = ({
|
|
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
|
};
|