Skip to content

Commit 2d807d6

Browse files
feat: rework navigation, update breadcrumbs (#418)
1 parent 13599e1 commit 2d807d6

File tree

29 files changed

+554
-461
lines changed

29 files changed

+554
-461
lines changed

src/components/NodeHostWrapper/NodeHostWrapper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const b = block('ydb-node-host-wrapper');
1616

1717
interface NodeHostWrapperProps {
1818
node: INodesPreparedEntity;
19-
getNodeRef?: (node?: NodeAddress) => string;
19+
getNodeRef?: (node?: NodeAddress) => string | null;
2020
}
2121

2222
export const NodeHostWrapper = ({node, getNodeRef}: NodeHostWrapperProps) => {

src/containers/App/Content.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
55

66
import {ThemeProvider} from '@gravity-ui/uikit';
77

8-
import routes, {createHref, CLUSTER_PAGES} from '../../routes';
8+
import routes, {createHref} from '../../routes';
99

1010
import Cluster from '../Cluster/Cluster';
1111
import Tenant from '../Tenant/Tenant';
@@ -23,6 +23,7 @@ import './App.scss';
2323
import PropTypes from 'prop-types';
2424
import HistoryContext from '../../contexts/HistoryContext';
2525
import Authentication from '../Authentication/Authentication';
26+
import {clusterTabsIds} from '../Cluster/utils';
2627

2728
const b = cn('app');
2829

@@ -44,7 +45,7 @@ export function Content(props) {
4445
<Route path={routes.tabletsFilters} component={TabletsFilters} />
4546
<Redirect
4647
to={createHref(routes.cluster, {
47-
activeTab: CLUSTER_PAGES.tenants.id,
48+
activeTab: clusterTabsIds.tenants,
4849
})}
4950
/>
5051
</Switch>

src/containers/AsideNavigation/AsideNavigation.tsx

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,15 @@ import {AsideHeader, MenuItem as AsideHeaderMenuItem, FooterItem} from '@gravity
99

1010
import signOutIcon from '../../assets/icons/signOut.svg';
1111
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';
1512
import ydbLogoIcon from '../../assets/icons/ydb.svg';
16-
import databasesIcon from '../../assets/icons/databases.svg';
1713
import userSecret from '../../assets/icons/user-secret.svg';
1814
import userChecked from '../../assets/icons/user-check.svg';
1915
import settingsIcon from '../../assets/icons/settings.svg';
2016
import supportIcon from '../../assets/icons/support.svg';
2117

2218
import {UserSettings} from '../UserSettings/UserSettings';
2319

24-
import routes, {createHref, CLUSTER_PAGES} from '../../routes';
20+
import routes, {createHref} from '../../routes';
2521

2622
import {logout} from '../../store/reducers/authentication';
2723
import {getParsedSettingValue, setSettingValue} from '../../store/reducers/settings/settings';
@@ -115,46 +111,8 @@ interface AsideNavigationProps {
115111
setSettingValue: (name: string, value: string) => void;
116112
}
117113

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[] = [];
158116

159117
enum Panel {
160118
UserSettings = 'UserSettings',
@@ -172,17 +130,13 @@ function AsideNavigation(props: AsideNavigationProps) {
172130

173131
const menuItems: AsideHeaderMenuItem[] = React.useMemo(() => {
174132
const {pathname} = location;
175-
const isClusterPage = pathname === '/cluster';
176133
const menuItems: AsideHeaderMenuItem[] = items.map((item) => {
177134
const locationKeysCoincidence = item.locationKeys?.filter((key) =>
178135
pathname.startsWith(key),
179136
);
180-
let current =
137+
const current =
181138
(locationKeysCoincidence && locationKeysCoincidence.length > 0) ||
182139
item.location.startsWith(pathname);
183-
if (isClusterPage && item.id !== CLUSTER_PAGES.tenants.id) {
184-
current = false;
185-
}
186140
return {
187141
id: item.id,
188142
title: item.title,

src/containers/Cluster/Cluster.scss

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,16 @@
22

33
.cluster {
44
overflow: auto;
5+
flex-grow: 1;
56

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;
289

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();
4911

50-
margin: 17px 0;
51-
}
12+
&__content {
13+
overflow: auto;
5214

53-
&__table-wrapper {
54-
display: flex;
55-
flex: 1 1 auto;
56-
flex-direction: column;
15+
height: 100%;
5716
}
5817
}

src/containers/Cluster/Cluster.tsx

Lines changed: 136 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,175 @@
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';
24
import cn from 'bem-cn-lite';
5+
import qs from 'qs';
6+
7+
import {Tabs} from '@gravity-ui/uikit';
38

49
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';
612

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';
820
import Tenants from '../Tenants/Tenants';
921
import {Nodes} from '../Nodes/Nodes';
1022
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';
1127

1228
import './Cluster.scss';
1329

1430
const b = cn('cluster');
1531

1632
interface ClusterProps {
17-
additionalTenantsInfo?: any;
18-
additionalNodesInfo?: any;
33+
additionalTenantsInfo?: unknown;
34+
additionalNodesInfo?: AdditionalNodesInfo;
1935
additionalClusterProps?: AdditionalClusterProps;
2036
additionalVersionsProps?: AdditionalVersionsProps;
2137
}
2238

2339
function Cluster({
40+
additionalClusterProps,
2441
additionalTenantsInfo,
2542
additionalNodesInfo,
26-
additionalClusterProps,
2743
additionalVersionsProps,
2844
}: 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 = () => {
32105
switch (activeTab) {
33-
case CLUSTER_PAGES.tenants.id: {
106+
case clusterTabsIds.tenants: {
34107
return <Tenants additionalTenantsInfo={additionalTenantsInfo} />;
35108
}
36-
case CLUSTER_PAGES.nodes.id: {
109+
case clusterTabsIds.nodes: {
37110
return <Nodes additionalNodesInfo={additionalNodesInfo} />;
38111
}
39-
case CLUSTER_PAGES.storage.id: {
112+
case clusterTabsIds.storage: {
40113
return <Storage additionalNodesInfo={additionalNodesInfo} />;
41114
}
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;
49131
}
50132
default: {
51133
return null;
52134
}
53135
}
54136
};
55137

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+
);
57173
}
58174

59175
export default Cluster;

0 commit comments

Comments
 (0)