ydb-embedded-ui 4.3.0 → 4.4.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 (76) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/containers/App/Content.js +2 -8
  3. package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
  4. package/dist/containers/Cluster/Cluster.scss +4 -0
  5. package/dist/containers/Cluster/Cluster.tsx +14 -8
  6. package/dist/{components → containers}/ClusterInfo/ClusterInfo.scss +39 -0
  7. package/dist/containers/ClusterInfo/ClusterInfo.tsx +207 -0
  8. package/dist/containers/ClusterInfo/utils.ts +13 -0
  9. package/dist/containers/Header/Header.tsx +9 -16
  10. package/dist/containers/Nodes/Nodes.tsx +4 -6
  11. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +2 -3
  12. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +4 -4
  13. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +4 -0
  14. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +2 -2
  15. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx +20 -26
  16. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +1 -1
  17. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +1 -1
  18. package/dist/containers/UserSettings/Setting.tsx +82 -0
  19. package/dist/containers/UserSettings/UserSettings.tsx +61 -99
  20. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.scss +59 -0
  21. package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx +98 -0
  22. package/dist/containers/Versions/NodesTable/NodesTable.tsx +150 -0
  23. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.scss +55 -0
  24. package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx +62 -0
  25. package/dist/containers/Versions/Versions.scss +32 -0
  26. package/dist/containers/Versions/Versions.tsx +121 -0
  27. package/dist/containers/Versions/groupNodes.ts +124 -0
  28. package/dist/containers/Versions/types.ts +16 -0
  29. package/dist/routes.ts +0 -6
  30. package/dist/services/api.ts +3 -0
  31. package/dist/store/reducers/cluster/cluster.ts +4 -0
  32. package/dist/store/reducers/cluster/types.ts +3 -2
  33. package/dist/store/reducers/clusterNodes/clusterNodes.tsx +64 -0
  34. package/dist/store/reducers/clusterNodes/types.ts +22 -0
  35. package/dist/store/reducers/index.ts +2 -8
  36. package/dist/store/reducers/partitions/partitions.ts +2 -2
  37. package/dist/store/reducers/partitions/types.ts +1 -1
  38. package/dist/types/additionalProps.ts +5 -0
  39. package/dist/types/versions.ts +9 -0
  40. package/dist/utils/constants.ts +0 -11
  41. package/dist/utils/hooks/useSetting.ts +5 -3
  42. package/dist/utils/versions/getVersionsColors.ts +98 -0
  43. package/dist/utils/versions/index.ts +3 -0
  44. package/dist/utils/versions/parseNodesToVersionsValues.ts +28 -0
  45. package/dist/utils/versions/parseVersion.ts +23 -0
  46. package/package.json +1 -1
  47. package/dist/components/ClusterInfo/ClusterInfo.tsx +0 -239
  48. package/dist/components/FullGroupViewer/FullGroupViewer.js +0 -147
  49. package/dist/components/FullGroupViewer/FullGroupViewer.scss +0 -35
  50. package/dist/components/GroupTreeViewer/GroupTreeViewer.js +0 -87
  51. package/dist/components/GroupTreeViewer/GroupTreeViewer.scss +0 -16
  52. package/dist/components/GroupViewer/GroupViewer.js +0 -100
  53. package/dist/components/GroupViewer/GroupViewer.scss +0 -45
  54. package/dist/components/PDiskViewer/PDiskViewer.js +0 -79
  55. package/dist/components/PDiskViewer/PDiskViewer.scss +0 -46
  56. package/dist/components/TabletsViewer/TabletsViewer.js +0 -44
  57. package/dist/components/TabletsViewer/TabletsViewer.scss +0 -40
  58. package/dist/components/VerticalBars/VerticalBars.scss +0 -15
  59. package/dist/components/VerticalBars/VerticalBars.tsx +0 -38
  60. package/dist/components/VerticalBars/index.ts +0 -1
  61. package/dist/containers/Group/Group.js +0 -97
  62. package/dist/containers/Group/Group.scss +0 -6
  63. package/dist/containers/Header/Host/Host.js +0 -66
  64. package/dist/containers/Header/Host/Host.scss +0 -50
  65. package/dist/containers/Pdisk/Pdisk.js +0 -156
  66. package/dist/containers/Pdisk/Pdisk.scss +0 -42
  67. package/dist/containers/Pool/Pool.js +0 -170
  68. package/dist/containers/Pool/Pool.scss +0 -35
  69. package/dist/containers/Vdisk/Vdisk.js +0 -158
  70. package/dist/containers/Vdisk/Vdisk.scss +0 -42
  71. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +0 -526
  72. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +0 -60
  73. package/dist/store/reducers/group.js +0 -49
  74. package/dist/store/reducers/pdisk.js +0 -51
  75. package/dist/store/reducers/pool.js +0 -42
  76. package/dist/store/reducers/vdisk.js +0 -49
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.4.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.3.0...v4.4.0) (2023-05-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * add Versions ([#394](https://github.com/ydb-platform/ydb-embedded-ui/issues/394)) ([d5abb58](https://github.com/ydb-platform/ydb-embedded-ui/commit/d5abb586a127135c5756a3aa5076060c0dce3fba))
9
+ * remove unsupported pages ([b2bc3b2](https://github.com/ydb-platform/ydb-embedded-ui/commit/b2bc3b22029679769bb0de73f2c33827028de8a8))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **ClusterInfo:** do not show response error on cancelled requests ([83501b5](https://github.com/ydb-platform/ydb-embedded-ui/commit/83501b50f0c266ba654858767ca89a2a3fa891ed))
15
+ * **Cluster:** remove padding from cluster page ([8138823](https://github.com/ydb-platform/ydb-embedded-ui/commit/8138823a9d5d3dbd1f086fb0bb23265d7faa8025))
16
+ * **Partitions:** fix columns titles ([4fe21a0](https://github.com/ydb-platform/ydb-embedded-ui/commit/4fe21a0dc149c7bca0611c74990756fbdc5fb273))
17
+ * **Partitions:** update Select empty value ([a7df6d1](https://github.com/ydb-platform/ydb-embedded-ui/commit/a7df6d1c86224a4534fac048cebc61b6f5a78fde))
18
+ * **UserSettings:** separate Setting, enable additional settings ([#396](https://github.com/ydb-platform/ydb-embedded-ui/issues/396)) ([e8a17a1](https://github.com/ydb-platform/ydb-embedded-ui/commit/e8a17a160c82212a181b1ef4e3b9f223db29907e))
19
+
3
20
  ## [4.3.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.2.1...v4.3.0) (2023-05-18)
4
21
 
5
22
 
@@ -10,16 +10,13 @@ import routes, {createHref, CLUSTER_PAGES} from '../../routes';
10
10
  import Cluster from '../Cluster/Cluster';
11
11
  import Tenant from '../Tenant/Tenant';
12
12
  import Node from '../Node/Node';
13
- import Pdisk from '../Pdisk/Pdisk';
14
- import Group from '../Group/Group';
15
- import Pool from '../Pool/Pool';
16
13
  import {Tablet} from '../Tablet';
17
14
  import TabletsFilters from '../TabletsFilters/TabletsFilters';
18
15
  import ReduxTooltip from '../ReduxTooltip/ReduxTooltip';
19
16
  import Header from '../Header/Header';
20
17
  import AppIcons from '../AppIcons/AppIcons';
21
18
 
22
- import {getSettingValue} from '../../store/reducers/settings';
19
+ import {getParsedSettingValue} from '../../store/reducers/settings';
23
20
  import {THEME_KEY} from '../../utils/constants';
24
21
 
25
22
  import './App.scss';
@@ -42,10 +39,7 @@ export function Content(props) {
42
39
  <Switch>
43
40
  <Route path={routes.cluster} component={Cluster} />
44
41
  <Route path={routes.tenant} component={Tenant} />
45
- <Route path={routes.pdisk} component={Pdisk} />
46
42
  <Route path={routes.node} component={Node} />
47
- <Route path={routes.group} component={Group} />
48
- <Route path={routes.pool} component={Pool} />
49
43
  <Route path={routes.tablet} component={Tablet} />
50
44
  <Route path={routes.tabletsFilters} component={TabletsFilters} />
51
45
  <Redirect
@@ -106,7 +100,7 @@ ContentWrapper.propTypes = {
106
100
 
107
101
  function mapStateToProps(state) {
108
102
  return {
109
- theme: getSettingValue(state, THEME_KEY),
103
+ theme: getParsedSettingValue(state, THEME_KEY),
110
104
  isAuthenticated: state.authentication.isAuthenticated,
111
105
  singleClusterMode: state.singleClusterMode,
112
106
  };
@@ -19,7 +19,7 @@ import userChecked from '../../assets/icons/user-check.svg';
19
19
  import settingsIcon from '../../assets/icons/settings.svg';
20
20
  import supportIcon from '../../assets/icons/support.svg';
21
21
 
22
- import UserSettings from '../UserSettings/UserSettings';
22
+ import {UserSettings} from '../UserSettings/UserSettings';
23
23
 
24
24
  import routes, {createHref, CLUSTER_PAGES} from '../../routes';
25
25
 
@@ -6,6 +6,10 @@
6
6
  padding: 0 20px;
7
7
  @include flex-container();
8
8
 
9
+ &_tab_cluster {
10
+ padding: 0px;
11
+ }
12
+
9
13
  &__tab {
10
14
  text-decoration: none;
11
15
 
@@ -1,15 +1,15 @@
1
+ import {useRouteMatch} from 'react-router';
1
2
  import cn from 'bem-cn-lite';
2
- //@ts-ignore
3
+
4
+ import type {AdditionalVersionsProps} from '../../types/additionalProps';
5
+ import routes, {CLUSTER_PAGES} from '../../routes';
6
+
7
+ import {ClusterInfo} from '../ClusterInfo/ClusterInfo';
3
8
  import Tenants from '../Tenants/Tenants';
4
- //@ts-ignore
5
9
  import {Nodes} from '../Nodes/Nodes';
6
- //@ts-ignore
7
10
  import Storage from '../Storage/Storage';
8
- import routes, {CLUSTER_PAGES} from '../../routes';
9
11
 
10
12
  import './Cluster.scss';
11
- import {useRouteMatch} from 'react-router';
12
- import ClusterInfo from '../../components/ClusterInfo/ClusterInfo';
13
13
 
14
14
  const b = cn('cluster');
15
15
 
@@ -17,6 +17,7 @@ interface ClusterProps {
17
17
  additionalClusterInfo?: any;
18
18
  additionalTenantsInfo?: any;
19
19
  additionalNodesInfo?: any;
20
+ additionalVersionsProps?: AdditionalVersionsProps;
20
21
  }
21
22
 
22
23
  function Cluster(props: ClusterProps) {
@@ -35,7 +36,12 @@ function Cluster(props: ClusterProps) {
35
36
  return <Storage {...props} />;
36
37
  }
37
38
  case CLUSTER_PAGES.cluster.id: {
38
- return <ClusterInfo additionalClusterInfo={props.additionalClusterInfo} />;
39
+ return (
40
+ <ClusterInfo
41
+ additionalClusterInfo={props.additionalClusterInfo}
42
+ additionalVersionsProps={props.additionalVersionsProps}
43
+ />
44
+ );
39
45
  }
40
46
  default: {
41
47
  return null;
@@ -43,7 +49,7 @@ function Cluster(props: ClusterProps) {
43
49
  }
44
50
  };
45
51
 
46
- return <div className={b()}>{renderRoutes()}</div>;
52
+ return <div className={b({tab: activeTab})}>{renderRoutes()}</div>;
47
53
  }
48
54
 
49
55
  export default Cluster;
@@ -1,7 +1,25 @@
1
1
  @import '../../styles/mixins';
2
2
 
3
3
  .cluster-info {
4
+ $_: &;
5
+ $progress_width: 100%;
6
+
7
+ display: flex;
8
+ flex: 1 1 auto;
9
+ flex-direction: column;
10
+
4
11
  width: 100%;
12
+
13
+ font-size: var(--yc-text-body-2-font-size);
14
+ line-height: var(--yc-text-body-2-line-height);
15
+
16
+ &__header {
17
+ padding: 20px;
18
+
19
+ border-bottom: 1px solid var(--yc-color-line-generic);
20
+ background: var(--yc-color-base-background);
21
+ }
22
+
5
23
  &__loader {
6
24
  display: flex;
7
25
  justify-content: center;
@@ -69,4 +87,25 @@
69
87
 
70
88
  margin-left: 5px;
71
89
  }
90
+ &__progress-label {
91
+ margin: 0 10px 0 0;
92
+
93
+ font-weight: 200;
94
+ }
95
+
96
+ &__version-progress {
97
+ display: flex;
98
+ align-items: center;
99
+
100
+ width: $progress_width;
101
+ margin-top: 20px;
102
+
103
+ & .yc-progress {
104
+ width: $progress_width;
105
+ }
106
+ }
107
+
108
+ & .yc-progress__stack {
109
+ cursor: pointer;
110
+ }
72
111
  }
@@ -0,0 +1,207 @@
1
+ import {useCallback, useEffect, useMemo} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
+ import {useLocation} from 'react-router';
4
+ import block from 'bem-cn-lite';
5
+ import qs from 'qs';
6
+
7
+ import {Link, Progress} from '@gravity-ui/uikit';
8
+
9
+ import EntityStatus from '../../components/EntityStatus/EntityStatus';
10
+ import ProgressViewer from '../../components/ProgressViewer/ProgressViewer';
11
+ import InfoViewer, {InfoViewerItem} from '../../components/InfoViewer/InfoViewer';
12
+ import {Tags} from '../../components/Tags';
13
+ import {Tablet} from '../../components/Tablet';
14
+ import {Icon} from '../../components/Icon';
15
+ import {Loader} from '../../components/Loader';
16
+ import {ResponseError} from '../../components/Errors/ResponseError';
17
+
18
+ import type {AdditionalVersionsProps} from '../../types/additionalProps';
19
+ import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
20
+ import {getClusterInfo} from '../../store/reducers/cluster/cluster';
21
+ import {getClusterNodes} from '../../store/reducers/clusterNodes/clusterNodes';
22
+ import {backend, customBackend} from '../../store';
23
+ import {setHeader} from '../../store/reducers/header';
24
+ import {formatStorageValues} from '../../utils';
25
+ import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
26
+ import {parseVersionsToVersionToColorMap, parseNodesToVersionsValues} from '../../utils/versions';
27
+ import routes, {CLUSTER_PAGES, createHref} from '../../routes';
28
+
29
+ import {Versions} from '../Versions/Versions';
30
+
31
+ import {compareTablets} from './utils';
32
+
33
+ import './ClusterInfo.scss';
34
+
35
+ const b = block('cluster-info');
36
+
37
+ interface ClusterInfoProps {
38
+ clusterTitle?: string;
39
+ additionalClusterInfo?: InfoViewerItem[];
40
+ additionalVersionsProps?: AdditionalVersionsProps;
41
+ }
42
+
43
+ export const ClusterInfo = ({
44
+ clusterTitle,
45
+ additionalClusterInfo = [],
46
+ additionalVersionsProps,
47
+ }: ClusterInfoProps) => {
48
+ const dispatch = useDispatch();
49
+ const location = useLocation();
50
+
51
+ const queryParams = qs.parse(location.search, {
52
+ ignoreQueryPrefix: true,
53
+ });
54
+ const {clusterName} = queryParams;
55
+
56
+ const {data: cluster, loading, wasLoaded, error} = useTypedSelector((state) => state.cluster);
57
+ const {
58
+ nodes,
59
+ loading: nodesLoading,
60
+ wasLoaded: nodesWasLoaded,
61
+ error: nodesError,
62
+ } = useTypedSelector((state) => state.clusterNodes);
63
+ const singleClusterMode = useTypedSelector((state) => state.singleClusterMode);
64
+
65
+ useEffect(() => {
66
+ dispatch(
67
+ setHeader([
68
+ {
69
+ text: CLUSTER_PAGES.cluster.title,
70
+ link: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.cluster.id}),
71
+ },
72
+ ]),
73
+ );
74
+ }, [dispatch]);
75
+
76
+ const fetchData = useCallback(() => {
77
+ dispatch(getClusterInfo(clusterName ? String(clusterName) : undefined));
78
+ dispatch(getClusterNodes());
79
+ }, [dispatch, clusterName]);
80
+
81
+ useAutofetcher(fetchData, [fetchData], true);
82
+
83
+ const versionToColor = useMemo(() => {
84
+ if (additionalVersionsProps?.getVersionToColorMap) {
85
+ return additionalVersionsProps?.getVersionToColorMap();
86
+ }
87
+ return parseVersionsToVersionToColorMap(cluster?.Versions);
88
+ }, [additionalVersionsProps, cluster]);
89
+
90
+ const versionsValues = useMemo(() => {
91
+ return parseNodesToVersionsValues(nodes, versionToColor);
92
+ }, [nodes, versionToColor]);
93
+
94
+ const onShowTooltip = (...args: Parameters<typeof showTooltip>) => {
95
+ dispatch(showTooltip(...args));
96
+ };
97
+
98
+ const onHideTooltip = () => {
99
+ dispatch(hideTooltip());
100
+ };
101
+
102
+ const getInfo = () => {
103
+ let link = backend + '/internal';
104
+
105
+ if (singleClusterMode && !customBackend) {
106
+ link = `/internal`;
107
+ }
108
+
109
+ const info: InfoViewerItem[] = [
110
+ {
111
+ label: 'Nodes',
112
+ value: (
113
+ <ProgressViewer
114
+ className={b('metric-field')}
115
+ value={cluster?.NodesAlive}
116
+ capacity={cluster?.NodesTotal}
117
+ />
118
+ ),
119
+ },
120
+ {
121
+ label: 'Load',
122
+ value: (
123
+ <ProgressViewer
124
+ className={b('metric-field')}
125
+ value={cluster?.LoadAverage}
126
+ capacity={cluster?.NumberOfCpus}
127
+ />
128
+ ),
129
+ },
130
+ {
131
+ label: 'Storage',
132
+ value: (
133
+ <ProgressViewer
134
+ className={b('metric-field')}
135
+ value={cluster?.StorageUsed}
136
+ capacity={cluster?.StorageTotal}
137
+ formatValues={formatStorageValues}
138
+ />
139
+ ),
140
+ },
141
+ {
142
+ label: 'Versions',
143
+ value: <div>{cluster?.Versions?.join(', ')}</div>,
144
+ },
145
+ ...additionalClusterInfo,
146
+ {
147
+ label: 'Internal viewer',
148
+ value: (
149
+ <Link href={link} target="_blank">
150
+ <Icon name="external" viewBox={'0 0 16 16'} width={16} height={16} />
151
+ </Link>
152
+ ),
153
+ },
154
+ ];
155
+
156
+ return info;
157
+ };
158
+
159
+ if ((loading && !wasLoaded) || (nodesLoading && !nodesWasLoaded)) {
160
+ return <Loader size="l" />;
161
+ }
162
+
163
+ if (error || nodesError) {
164
+ return <ResponseError error={error || nodesError} />;
165
+ }
166
+
167
+ return (
168
+ <div className={b()}>
169
+ <div className={b('header')}>
170
+ <div className="info">
171
+ <div className={b('common')}>
172
+ <div className={b('url')}>
173
+ <EntityStatus
174
+ size="m"
175
+ status={cluster?.Overall}
176
+ name={clusterTitle ?? cluster?.Name ?? 'Unknown cluster'}
177
+ />
178
+ </div>
179
+
180
+ {cluster?.DataCenters && <Tags tags={cluster?.DataCenters} />}
181
+
182
+ <div className={b('system-tablets')}>
183
+ {cluster?.SystemTablets &&
184
+ cluster.SystemTablets.sort(compareTablets).map(
185
+ (tablet, tabletIndex) => (
186
+ <Tablet
187
+ onMouseEnter={onShowTooltip}
188
+ onMouseLeave={onHideTooltip}
189
+ key={tabletIndex}
190
+ tablet={tablet}
191
+ />
192
+ ),
193
+ )}
194
+ </div>
195
+ </div>
196
+ <InfoViewer dots={true} info={getInfo()} />
197
+ </div>
198
+ <div className={b('version-progress')}>
199
+ <h3 className={b('progress-label')}>Versions</h3>
200
+ <Progress value={100} stack={versionsValues} />
201
+ </div>
202
+ </div>
203
+
204
+ <Versions nodes={nodes} versionToColor={versionToColor} />
205
+ </div>
206
+ );
207
+ };
@@ -0,0 +1,13 @@
1
+ import {TTabletStateInfo, EType} from '../../types/api/tablet';
2
+
3
+ export const compareTablets = (tablet1: TTabletStateInfo, tablet2: TTabletStateInfo) => {
4
+ if (tablet1.Type === EType.TxAllocator) {
5
+ return 1;
6
+ }
7
+
8
+ if (tablet2.Type === EType.TxAllocator) {
9
+ return -1;
10
+ }
11
+
12
+ return 0;
13
+ };
@@ -1,16 +1,16 @@
1
1
  import React, {useEffect} from 'react';
2
- import {useDispatch, useSelector} from 'react-redux';
2
+ import {useDispatch} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
- import {useHistory, useLocation} from 'react-router';
4
+ import {useHistory} from 'react-router';
5
5
  import {Breadcrumbs, BreadcrumbsItem, Link} from '@gravity-ui/uikit';
6
6
 
7
7
  import Divider from '../../components/Divider/Divider';
8
8
  import {Icon} from '../../components/Icon';
9
9
 
10
- import {clusterName as clusterNameLocation, backend, customBackend} from '../../store';
11
- import {getClusterInfo} from '../../store/reducers/cluster/cluster';
10
+ import {backend, customBackend} from '../../store';
12
11
  import {getHostInfo} from '../../store/reducers/host';
13
12
  import {HeaderItemType} from '../../store/reducers/header';
13
+ import {useTypedSelector} from '../../utils/hooks';
14
14
 
15
15
  import './Header.scss';
16
16
 
@@ -31,26 +31,19 @@ interface HeaderProps {
31
31
 
32
32
  function Header({clusterName}: HeaderProps) {
33
33
  const dispatch = useDispatch();
34
- const {data: host}: {data: {ClusterName?: string}} = useSelector((state: any) => state.host);
34
+
35
35
  const {singleClusterMode, header}: {singleClusterMode: boolean; header: HeaderItemType[]} =
36
- useSelector((state: any) => state);
36
+ useTypedSelector((state) => state);
37
+ const {data: host} = useTypedSelector((state) => state.host);
37
38
 
38
- const location = useLocation();
39
39
  const history = useHistory();
40
40
 
41
- const {pathname} = location;
42
-
43
41
  useEffect(() => {
44
- const isClustersPage = pathname.includes('/clusters');
45
-
46
- if (!isClustersPage && !clusterName && !singleClusterMode) {
47
- dispatch(getClusterInfo(clusterNameLocation));
48
- }
49
42
  dispatch(getHostInfo());
50
- }, []);
43
+ }, [dispatch]);
51
44
 
52
45
  const renderHeader = () => {
53
- const clusterNameFinal = singleClusterMode ? host.ClusterName : clusterName;
46
+ const clusterNameFinal = singleClusterMode ? host?.ClusterName : clusterName;
54
47
 
55
48
  let link = backend + '/internal';
56
49
 
@@ -21,7 +21,7 @@ import {
21
21
  DEFAULT_TABLE_SETTINGS,
22
22
  USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
23
23
  } from '../../utils/constants';
24
- import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
24
+ import {useAutofetcher, useSetting, useTypedSelector} from '../../utils/hooks';
25
25
  import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
26
26
 
27
27
  import {setHeader} from '../../store/reducers/header';
@@ -33,7 +33,7 @@ import {
33
33
  resetNodesState,
34
34
  getComputeNodes,
35
35
  } from '../../store/reducers/nodes';
36
- import {changeFilter, getSettingValue} from '../../store/reducers/settings';
36
+ import {changeFilter} from '../../store/reducers/settings';
37
37
  import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
38
38
 
39
39
  import {isDatabaseEntityType} from '../Tenant/utils/schema';
@@ -72,14 +72,12 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
72
72
 
73
73
  const nodes = useTypedSelector(getFilteredPreparedNodesList);
74
74
 
75
- const useNodesEndpoint = useTypedSelector((state) =>
76
- getSettingValue(state, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY),
77
- );
75
+ const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
78
76
 
79
77
  const fetchNodes = useCallback(() => {
80
78
  // For not DB entities we always use /compute endpoint instead of /nodes
81
79
  // since /nodes can return data only for tenants
82
- if (path && (!JSON.parse(useNodesEndpoint) || !isDatabaseEntityType(type))) {
80
+ if (path && (useNodesEndpoint || !isDatabaseEntityType(type))) {
83
81
  dispatch(getComputeNodes(path));
84
82
  } else {
85
83
  dispatch(getNodes({tenant: path}));
@@ -1,9 +1,8 @@
1
1
  import React from 'react';
2
- import {useSelector} from 'react-redux';
3
2
  import cn from 'bem-cn-lite';
4
3
 
5
4
  import {INVERTED_DISKS_KEY} from '../../../utils/constants';
6
- import {getSettingValue} from '../../../store/reducers/settings';
5
+ import {useSetting} from '../../../utils/hooks';
7
6
 
8
7
  import './DiskStateProgressBar.scss';
9
8
 
@@ -35,7 +34,7 @@ export function DiskStateProgressBar({
35
34
  severity,
36
35
  compact,
37
36
  }: DiskStateProgressBarProps) {
38
- const inverted = useSelector((state) => JSON.parse(getSettingValue(state, INVERTED_DISKS_KEY)));
37
+ const [inverted] = useSetting<boolean | undefined>(INVERTED_DISKS_KEY);
39
38
 
40
39
  const renderAllocatedPercent = () => {
41
40
  if (compact) {
@@ -28,16 +28,16 @@ export const ReadSessionHeader = () => (
28
28
  export const WriteLagsHeader = () => (
29
29
  <LabelWithPopover
30
30
  className={b('lags')}
31
- text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.READ_LAGS]}
32
- popoverContent={<LagPopoverContent text={i18n('lagsPopover.readLags')} type="read" />}
31
+ text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.WRITE_LAGS]}
32
+ popoverContent={<LagPopoverContent text={i18n('lagsPopover.writeLags')} type="write" />}
33
33
  />
34
34
  );
35
35
 
36
36
  export const ReadLagsHeader = () => (
37
37
  <LabelWithPopover
38
38
  className={b('lags')}
39
- text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.WRITE_LAGS]}
40
- popoverContent={<LagPopoverContent text={i18n('lagsPopover.writeLags')} type="write" />}
39
+ text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.READ_LAGS]}
40
+ popoverContent={<LagPopoverContent text={i18n('lagsPopover.readLags')} type="read" />}
41
41
  />
42
42
  );
43
43
 
@@ -16,6 +16,10 @@
16
16
  width: 220px;
17
17
  }
18
18
 
19
+ &__select-option_empty {
20
+ color: var(--yc-color-text-hint);
21
+ }
22
+
19
23
  &__search {
20
24
  @include search();
21
25
  &_partition {
@@ -113,7 +113,7 @@ export const Partitions = ({path}: PartitionsProps) => {
113
113
  selectedConsumer && consumers && !consumers.includes(selectedConsumer);
114
114
 
115
115
  if (isTopicWithoutConsumers || wrongSelectedConsumer) {
116
- dispatch(setSelectedConsumer());
116
+ dispatch(setSelectedConsumer(''));
117
117
  }
118
118
  }, [dispatch, topicWasLoaded, selectedConsumer, consumers]);
119
119
 
@@ -125,7 +125,7 @@ export const Partitions = ({path}: PartitionsProps) => {
125
125
  setHiddenColumns(newHiddenColumns);
126
126
  };
127
127
 
128
- const handleSelectedConsumerChange = (value?: string) => {
128
+ const handleSelectedConsumerChange = (value: string) => {
129
129
  dispatch(setSelectedConsumer(value));
130
130
  };
131
131
 
@@ -2,7 +2,7 @@ import {useEffect, useMemo, useState} from 'react';
2
2
  import {escapeRegExp} from 'lodash/fp';
3
3
 
4
4
  import {TableColumnSetupItem} from '@gravity-ui/uikit/build/esm/components/Table/hoc/withTableSettings/withTableSettings';
5
- import {Select, TableColumnSetup} from '@gravity-ui/uikit';
5
+ import {Select, SelectOption, TableColumnSetup} from '@gravity-ui/uikit';
6
6
 
7
7
  import type {ValueOf} from '../../../../../types/common';
8
8
 
@@ -15,8 +15,8 @@ import {b} from '../Partitions';
15
15
 
16
16
  interface PartitionsControlsProps {
17
17
  consumers: string[] | undefined;
18
- selectedConsumer: string | undefined;
19
- onSelectedConsumerChange: (consumer: string | undefined) => void;
18
+ selectedConsumer: string;
19
+ onSelectedConsumerChange: (consumer: string) => void;
20
20
  selectDisabled: boolean;
21
21
  partitions: PreparedPartitionDataWithHosts[] | undefined;
22
22
  onSearchChange: (filteredPartitions: PreparedPartitionDataWithHosts[]) => void;
@@ -39,9 +39,6 @@ export const PartitionsControls = ({
39
39
  const [generalSearchValue, setGeneralSearchValue] = useState('');
40
40
  const [partitionIdSearchValue, setPartitionIdSearchValue] = useState('');
41
41
 
42
- // Manual select control to enforce single-select behaviour for multiple select type
43
- const [consumerSelectOpen, setConsumerSelectOpen] = useState(false);
44
-
45
42
  useEffect(() => {
46
43
  if (!partitions) {
47
44
  return;
@@ -83,16 +80,17 @@ export const PartitionsControls = ({
83
80
  onSearchChange(filteredPartitions);
84
81
  }, [partitionIdSearchValue, generalSearchValue, partitions, onSearchChange]);
85
82
 
86
- const consumersToSelect = useMemo(
87
- () =>
88
- consumers
83
+ const consumersToSelect = useMemo(() => {
84
+ const options =
85
+ consumers && consumers.length
89
86
  ? consumers.map((consumer) => ({
90
87
  value: consumer,
91
88
  content: consumer,
92
89
  }))
93
- : undefined,
94
- [consumers],
95
- );
90
+ : [];
91
+
92
+ return [{value: '', content: i18n('controls.consumerSelector.emptyOption')}, ...options];
93
+ }, [consumers]);
96
94
 
97
95
  const columnsToSelect = useMemo(() => {
98
96
  return initialColumnsIds.map((id) => {
@@ -106,10 +104,7 @@ export const PartitionsControls = ({
106
104
  }, [initialColumnsIds, hiddenColumns]);
107
105
 
108
106
  const handleConsumerSelectChange = (value: string[]) => {
109
- // As we have selector with multiple options, the first value corresponds to previous value
110
- // The second value is currently chosen consumer
111
- onSelectedConsumerChange(value[1]);
112
- setConsumerSelectOpen(false);
107
+ onSelectedConsumerChange(value[0]);
113
108
  };
114
109
 
115
110
  const handlePartitionIdSearchChange = (value: string) => {
@@ -137,25 +132,24 @@ export const PartitionsControls = ({
137
132
  onHiddenColumnsChange(result);
138
133
  };
139
134
 
135
+ const renderOption = (option: SelectOption) => {
136
+ return (
137
+ <div className={b('select-option', {empty: option.value === ''})}>{option.content}</div>
138
+ );
139
+ };
140
+
140
141
  return (
141
142
  <div className={b('controls')}>
142
143
  <Select
143
144
  className={b('consumer-select')}
144
- placeholder={i18n('controls.consumerSelector.placeholder')}
145
145
  label={i18n('controls.consumerSelector')}
146
146
  options={consumersToSelect}
147
- value={[selectedConsumer || '']}
147
+ value={[selectedConsumer]}
148
148
  onUpdate={handleConsumerSelectChange}
149
149
  filterable={consumers && consumers.length > 5}
150
150
  disabled={selectDisabled || !consumers || !consumers.length}
151
- open={consumerSelectOpen}
152
- onOpenChange={setConsumerSelectOpen}
153
- // Although only one value could be selected
154
- // Multiple type Select is used
155
- // The reason - we need Select to be able to work with no value
156
- // And it is easier to make multiple Select close on value change
157
- // Than to enable single select to work with empty values
158
- multiple
151
+ renderOption={renderOption}
152
+ renderSelectedOption={renderOption}
159
153
  />
160
154
  <Search
161
155
  onChange={handlePartitionIdSearchChange}