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.
Files changed (57) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +1 -1
  3. package/dist/containers/App/Content.js +3 -2
  4. package/dist/containers/AsideNavigation/AsideNavigation.tsx +4 -50
  5. package/dist/containers/Cluster/Cluster.scss +7 -48
  6. package/dist/containers/Cluster/Cluster.tsx +136 -20
  7. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +34 -17
  8. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +57 -91
  9. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.scss +48 -0
  10. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +34 -0
  11. package/dist/containers/Cluster/utils.ts +34 -0
  12. package/dist/containers/Header/Header.scss +0 -24
  13. package/dist/containers/Header/Header.tsx +14 -44
  14. package/dist/containers/Node/Node.tsx +22 -20
  15. package/dist/containers/Nodes/Nodes.tsx +0 -16
  16. package/dist/containers/Nodes/getNodesColumns.tsx +1 -1
  17. package/dist/containers/Storage/Storage.js +1 -11
  18. package/dist/containers/Tablet/Tablet.tsx +28 -0
  19. package/dist/containers/TabletsFilters/TabletsFilters.js +16 -1
  20. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +1 -1
  21. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +3 -0
  22. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -3
  23. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +4 -6
  24. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +56 -53
  25. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +1 -1
  26. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +1 -1
  27. package/dist/containers/Tenant/Preview/Preview.js +1 -1
  28. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +26 -22
  29. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +10 -3
  30. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +8 -1
  31. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/shared.ts +1 -6
  32. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -1
  33. package/dist/containers/Tenant/Tenant.tsx +7 -12
  34. package/dist/containers/Tenant/utils/schemaActions.ts +1 -1
  35. package/dist/containers/Tenants/Tenants.js +18 -28
  36. package/dist/containers/Tenants/Tenants.scss +2 -4
  37. package/dist/containers/UserSettings/i18n/en.json +2 -2
  38. package/dist/containers/UserSettings/i18n/ru.json +2 -2
  39. package/dist/containers/UserSettings/settings.ts +4 -4
  40. package/dist/containers/Versions/Versions.scss +0 -4
  41. package/dist/containers/Versions/Versions.tsx +74 -66
  42. package/dist/routes.ts +0 -7
  43. package/dist/services/api.ts +6 -2
  44. package/dist/store/reducers/clusterNodes/clusterNodes.tsx +4 -0
  45. package/dist/store/reducers/index.ts +3 -1
  46. package/dist/store/reducers/overview/overview.ts +109 -0
  47. package/dist/store/reducers/overview/types.ts +24 -0
  48. package/dist/store/reducers/{schema.ts → schema/schema.ts} +24 -50
  49. package/dist/{types/store/schema.ts → store/reducers/schema/types.ts} +16 -15
  50. package/dist/store/reducers/settings/settings.ts +5 -3
  51. package/dist/types/api/query.ts +78 -44
  52. package/dist/types/store/explainQuery.ts +2 -2
  53. package/dist/types/store/query.ts +4 -2
  54. package/dist/utils/constants.ts +3 -1
  55. package/dist/utils/nodes.ts +1 -1
  56. package/dist/utils/query.ts +3 -3
  57. 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, CLUSTER_PAGES} from '../../routes';
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: CLUSTER_PAGES.tenants.id,
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, CLUSTER_PAGES} from '../../routes';
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
- const items: MenuItem[] = [
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
- let current =
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
- padding: 0 20px;
7
- @include flex-container();
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
- &__tooltip {
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
- margin: 17px 0;
51
- }
12
+ &__content {
13
+ overflow: auto;
52
14
 
53
- &__table-wrapper {
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 {useRouteMatch} from 'react-router';
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 routes, {CLUSTER_PAGES} from '../../routes';
10
+ import type {AdditionalNodesInfo} from '../../utils/nodes';
11
+ import routes from '../../routes';
6
12
 
7
- import {ClusterInfo} from './ClusterInfo/ClusterInfo';
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?: any;
18
- additionalNodesInfo?: any;
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 match = useRouteMatch<{activeTab?: string}>(routes.cluster);
30
- const activeTab = match?.params?.activeTab ?? CLUSTER_PAGES.tenants.id;
31
- const renderRoutes = () => {
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 CLUSTER_PAGES.tenants.id: {
106
+ case clusterTabsIds.tenants: {
34
107
  return <Tenants additionalTenantsInfo={additionalTenantsInfo} />;
35
108
  }
36
- case CLUSTER_PAGES.nodes.id: {
109
+ case clusterTabsIds.nodes: {
37
110
  return <Nodes additionalNodesInfo={additionalNodesInfo} />;
38
111
  }
39
- case CLUSTER_PAGES.storage.id: {
112
+ case clusterTabsIds.storage: {
40
113
  return <Storage additionalNodesInfo={additionalNodesInfo} />;
41
114
  }
42
- case CLUSTER_PAGES.cluster.id: {
43
- return (
44
- <ClusterInfo
45
- additionalClusterProps={additionalClusterProps}
46
- additionalVersionsProps={additionalVersionsProps}
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 <div className={b({tab: activeTab})}>{renderRoutes()}</div>;
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
- display: flex;
8
- flex: 1 1 auto;
9
- flex-direction: column;
6
+ &__header {
7
+ display: flex;
10
8
 
11
- width: 100%;
9
+ width: fit-content;
10
+ margin-bottom: 20px;
12
11
 
13
- font-size: var(--yc-text-body-2-font-size);
14
- line-height: var(--yc-text-body-2-line-height);
12
+ cursor: pointer;
15
13
 
16
- &__header {
17
- padding: 20px;
14
+ &__expand-button {
15
+ margin-left: 6px;
18
16
 
19
- border-bottom: 1px solid var(--yc-color-line-generic);
20
- background: var(--yc-color-base-background);
17
+ &_rotate {
18
+ transform: rotate(180deg);
19
+ }
20
+ }
21
21
  }
22
22
 
23
- &__title {
24
- display: flex;
25
- align-items: center;
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
- margin-top: 16px;
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-bottom: 2px;
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
- 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} 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(CLUSTER_INFO_HIDDEN_KEY, false);
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
 
@@ -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
- <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
  };