diff --git a/src/containers/App/Content.tsx b/src/containers/App/Content.tsx
index 5b2293f126..91d526b85f 100644
--- a/src/containers/App/Content.tsx
+++ b/src/containers/App/Content.tsx
@@ -3,7 +3,7 @@ import {Switch, Route, Redirect, RedirectProps} from 'react-router-dom';
import cn from 'bem-cn-lite';
import {connect, useDispatch} from 'react-redux';
-import routes, {createHref} from '../../routes';
+import routes from '../../routes';
import {Clusters} from '../Clusters/Clusters';
import Cluster from '../Cluster/Cluster';
@@ -15,7 +15,7 @@ import Header from '../Header/Header';
import Authentication from '../Authentication/Authentication';
import {getUser} from '../../store/reducers/authentication/authentication';
-import {clusterTabsIds} from '../Cluster/utils';
+import {getClusterPath} from '../Cluster/utils';
import {useSlots} from '../../components/slots';
import {useTypedSelector} from '../../utils/hooks';
import {
@@ -108,14 +108,7 @@ export function Content(props: ContentProps) {
const redirect = slots.get(RedirectSlot);
const redirectProps: RedirectProps =
- redirect?.props ??
- (singleClusterMode
- ? {
- to: createHref(routes.cluster, {
- activeTab: clusterTabsIds.tenants,
- }),
- }
- : {to: routes.clusters});
+ redirect?.props ?? (singleClusterMode ? {to: getClusterPath()} : {to: routes.clusters});
let mainPage: RawBreadcrumbItem | undefined;
if (!singleClusterMode) {
diff --git a/src/containers/Cluster/Cluster.scss b/src/containers/Cluster/Cluster.scss
index fcf32aec36..929dda495d 100644
--- a/src/containers/Cluster/Cluster.scss
+++ b/src/containers/Cluster/Cluster.scss
@@ -9,6 +9,21 @@
@include flex-container();
+ &__header {
+ padding: 20px 0;
+ }
+
+ &__title {
+ font-weight: var(--g-text-header-font-weight);
+ @include header-1-typography();
+ }
+
+ &__title-skeleton {
+ width: 20%;
+ min-width: 200px;
+ height: var(--g-text-header-1-line-height);
+ }
+
&__tabs {
position: sticky;
left: 0;
diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx
index b0e36ff3e5..c225fb8ec1 100644
--- a/src/containers/Cluster/Cluster.tsx
+++ b/src/containers/Cluster/Cluster.tsx
@@ -1,10 +1,10 @@
import {useEffect, useMemo, useRef} from 'react';
-import {useLocation, useRouteMatch} from 'react-router';
+import {Redirect, Switch, Route, useLocation, useRouteMatch} from 'react-router';
import {useDispatch} from 'react-redux';
import cn from 'bem-cn-lite';
import qs from 'qs';
-import {Tabs} from '@gravity-ui/uikit';
+import {Skeleton, Tabs} from '@gravity-ui/uikit';
import type {
AdditionalClusterProps,
@@ -12,10 +12,10 @@ import type {
AdditionalVersionsProps,
AdditionalNodesProps,
} from '../../types/additionalProps';
-import routes from '../../routes';
+import routes, {getLocationObjectFromHref} from '../../routes';
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
-import {getClusterInfo} from '../../store/reducers/cluster/cluster';
+import {getClusterInfo, updateDefaultClusterTab} from '../../store/reducers/cluster/cluster';
import {getClusterNodes} from '../../store/reducers/clusterNodes/clusterNodes';
import {parseNodesToVersionsValues, parseVersionsToVersionToColorMap} from '../../utils/versions';
import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
@@ -25,9 +25,11 @@ import {Tenants} from '../Tenants/Tenants';
import {StorageWrapper} from '../Storage/StorageWrapper';
import {NodesWrapper} from '../Nodes/NodesWrapper';
import {Versions} from '../Versions/Versions';
+import EntityStatus from '../../components/EntityStatus/EntityStatus';
+import {CLUSTER_DEFAULT_TITLE} from '../../utils/constants';
import {ClusterInfo} from './ClusterInfo/ClusterInfo';
-import {ClusterTab, clusterTabs, clusterTabsIds, getClusterPath} from './utils';
+import {ClusterTab, clusterTabs, clusterTabsIds, getClusterPath, isClusterTab} from './utils';
import './Cluster.scss';
@@ -50,8 +52,7 @@ function Cluster({
const dispatch = useDispatch();
- const match = useRouteMatch<{activeTab: string}>(routes.cluster);
- const {activeTab = clusterTabsIds.tenants} = match?.params || {};
+ const activeTab = useClusterTab();
const location = useLocation();
const queryParams = qs.parse(location.search, {
@@ -101,57 +102,40 @@ function Cluster({
return parseNodesToVersionsValues(nodes, versionToColor);
}, [nodes, versionToColor]);
- const renderTab = () => {
- switch (activeTab) {
- case clusterTabsIds.tenants: {
- return ;
- }
- case clusterTabsIds.nodes: {
- return (
-
- );
- }
- case clusterTabsIds.storage: {
- return (
-
- );
- }
- case clusterTabsIds.versions: {
- return ;
- }
- default: {
- return null;
- }
+ const getClusterTitle = () => {
+ if (infoLoading) {
+ return ;
}
+
+ return (
+
+ );
};
return (
-
-
+
{getClusterTitle()}
{
const path = getClusterPath(id as ClusterTab, queryParams);
return (
-
+ {
+ dispatch(updateDefaultClusterTab(id));
+ }}
+ >
{node}
);
@@ -159,9 +143,89 @@ function Cluster({
/>
-
{renderTab()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
+function useClusterTab() {
+ const dispatch = useDispatch();
+
+ const defaultTab = useTypedSelector((state) => state.cluster.defaultClusterTab);
+
+ const match = useRouteMatch<{activeTab: string}>(routes.cluster);
+
+ const {activeTab: activeTabFromParams} = match?.params || {};
+ let activeTab: ClusterTab;
+ if (isClusterTab(activeTabFromParams)) {
+ activeTab = activeTabFromParams;
+ } else {
+ activeTab = defaultTab;
+ }
+
+ useEffect(() => {
+ if (activeTab !== defaultTab) {
+ dispatch(updateDefaultClusterTab(activeTab));
+ }
+ }, [activeTab, defaultTab, dispatch]);
+
+ return activeTab;
+}
+
export default Cluster;
diff --git a/src/containers/Cluster/ClusterInfo/ClusterInfo.scss b/src/containers/Cluster/ClusterInfo/ClusterInfo.scss
index beb86dc6ed..6e6b02b53d 100644
--- a/src/containers/Cluster/ClusterInfo/ClusterInfo.scss
+++ b/src/containers/Cluster/ClusterInfo/ClusterInfo.scss
@@ -1,52 +1,12 @@
@import '../../../styles/mixins';
.cluster-info {
- position: sticky;
- left: 0;
-
- width: 100%;
padding-top: 20px;
- &__header {
- display: flex;
-
- width: fit-content;
- margin-bottom: 20px;
-
- cursor: pointer;
-
- &__expand-button {
- margin-left: 6px;
-
- &_rotate {
- transform: rotate(180deg);
- }
- }
- }
-
- &__title .entity-status__name {
- font-weight: var(--g-text-header-font-weight);
- @include header-1-typography();
- }
-
- &__title-skeleton {
- width: 20%;
- min-width: 200px;
- height: var(--g-text-header-1-line-height);
- }
-
&__error {
@include body-2-typography();
}
- &__info {
- margin-bottom: 20px;
-
- &_hidden {
- display: none;
- }
- }
-
&__system-tablets {
display: flex;
flex-wrap: wrap;
diff --git a/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx b/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx
index 9dd34a2e95..51c304c211 100644
--- a/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx
+++ b/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx
@@ -1,15 +1,11 @@
import block from 'bem-cn-lite';
-import {Skeleton} from '@gravity-ui/uikit';
-
-import EntityStatus from '../../../components/EntityStatus/EntityStatus';
import {ProgressViewer} from '../../../components/ProgressViewer/ProgressViewer';
import InfoViewer, {InfoViewerItem} from '../../../components/InfoViewer/InfoViewer';
import {Tags} from '../../../components/Tags';
import {Tablet} from '../../../components/Tablet';
import {ResponseError} from '../../../components/Errors/ResponseError';
import {ExternalLinkWithIcon} from '../../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
-import {Icon} from '../../../components/Icon/Icon';
import {ContentWithPopup} from '../../../components/ContentWithPopup/ContentWithPopup';
import type {IResponseError} from '../../../types/api/error';
@@ -18,13 +14,9 @@ import type {VersionValue} from '../../../types/versions';
import type {TClusterInfo} from '../../../types/api/cluster';
import {backend, customBackend} from '../../../store';
import {formatStorageValues} from '../../../utils/dataFormatters/dataFormatters';
-import {useSetting, useTypedSelector} from '../../../utils/hooks';
+import {useTypedSelector} from '../../../utils/hooks';
import {formatBytes, getSizeWithSignificantDigits} from '../../../utils/bytesParsers';
-import {
- CLUSTER_DEFAULT_TITLE,
- CLUSTER_INFO_HIDDEN_KEY,
- DEVELOPER_UI_TITLE,
-} from '../../../utils/constants';
+import {DEVELOPER_UI_TITLE} from '../../../utils/constants';
import type {
ClusterGroupsStats,
DiskErasureGroupsStats,
@@ -217,12 +209,6 @@ export const ClusterInfo = ({
}: ClusterInfoProps) => {
const singleClusterMode = useTypedSelector((state) => state.singleClusterMode);
- const [clusterInfoHidden, setClusterInfoHidden] = useSetting(CLUSTER_INFO_HIDDEN_KEY);
-
- const togleClusterInfoVisibility = () => {
- setClusterInfoHidden(!clusterInfoHidden);
- };
-
let internalLink = backend + '/internal';
if (singleClusterMode && !customBackend) {
@@ -248,33 +234,9 @@ export const ClusterInfo = ({
return ;
};
- const getClusterTitle = () => {
- if (loading) {
- return ;
- }
-
- return (
-
- );
- };
-
return (
-
- {getClusterTitle()}
-
-
-
{getContent()}
+
{getContent()}
);
};
diff --git a/src/containers/Cluster/utils.tsx b/src/containers/Cluster/utils.tsx
index d4d3768764..3ea4c3816c 100644
--- a/src/containers/Cluster/utils.tsx
+++ b/src/containers/Cluster/utils.tsx
@@ -2,6 +2,7 @@ import type {ValueOf} from '../../types/common';
import routes, {createHref} from '../../routes';
export const clusterTabsIds = {
+ overview: 'overview',
tenants: 'tenants',
nodes: 'nodes',
storage: 'storage',
@@ -10,6 +11,11 @@ export const clusterTabsIds = {
export type ClusterTab = ValueOf;
+const overview = {
+ id: clusterTabsIds.overview,
+ title: 'Overview',
+};
+
const tenants = {
id: clusterTabsIds.tenants,
title: 'Databases',
@@ -27,8 +33,12 @@ const versions = {
title: 'Versions',
};
-export const clusterTabs = [tenants, nodes, storage, versions];
+export const clusterTabs = [overview, tenants, nodes, storage, versions];
+
+export function isClusterTab(tab: any): tab is ClusterTab {
+ return Object.values(clusterTabsIds).includes(tab);
+}
-export const getClusterPath = (activeTab: ClusterTab = clusterTabsIds.tenants, query = {}) => {
- return createHref(routes.cluster, {activeTab}, query);
+export const getClusterPath = (activeTab?: ClusterTab, query = {}) => {
+ return createHref(routes.cluster, activeTab ? {activeTab} : undefined, query);
};
diff --git a/src/containers/Clusters/columns.tsx b/src/containers/Clusters/columns.tsx
index 2458fbf39d..e6920d600d 100644
--- a/src/containers/Clusters/columns.tsx
+++ b/src/containers/Clusters/columns.tsx
@@ -26,7 +26,7 @@ export const CLUSTERS_COLUMNS: Column[] = [
const backend = balancer && removeViewerPathname(balancer);
- const clusterPath = getClusterPath(clusterTabsIds.tenants, {backend, clusterName});
+ const clusterPath = getClusterPath(undefined, {backend, clusterName});
const clusterStatus = row.cluster?.Overall;
diff --git a/src/containers/Node/Node.tsx b/src/containers/Node/Node.tsx
index 3af9d5ece9..0f7f986450 100644
--- a/src/containers/Node/Node.tsx
+++ b/src/containers/Node/Node.tsx
@@ -22,8 +22,6 @@ import {useTypedSelector} from '../../utils/hooks';
import type {AdditionalNodesProps} from '../../types/additionalProps';
-import {clusterTabsIds} from '../Cluster/utils';
-
import './Node.scss';
const b = cn('node');
@@ -75,7 +73,6 @@ function Node(props: NodeProps) {
dispatch(
setHeaderBreadcrumbs('node', {
- clusterTab: clusterTabsIds.nodes,
tenantName,
nodeId,
}),
diff --git a/src/routes.ts b/src/routes.ts
index cb6a282590..e8222c6ef6 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -76,4 +76,9 @@ export function createHref(
export const createExternalUILink = (query = {}) =>
createHref(window.location.pathname, undefined, query);
+export function getLocationObjectFromHref(href: string) {
+ const {pathname, search, hash} = new URL(href, 'http://localhost');
+ return {pathname, search, hash};
+}
+
export default routes;
diff --git a/src/services/settings.ts b/src/services/settings.ts
index 10da702d4f..deb2b47793 100644
--- a/src/services/settings.ts
+++ b/src/services/settings.ts
@@ -2,7 +2,6 @@ import {TENANT_PAGES_IDS} from '../store/reducers/tenant/constants';
import {
ASIDE_HEADER_COMPACT_KEY,
- CLUSTER_INFO_HIDDEN_KEY,
INVERTED_DISKS_KEY,
LANGUAGE_KEY,
LAST_USED_QUERY_ACTION_KEY,
@@ -36,7 +35,6 @@ export const DEFAULT_USER_SETTINGS: SettingsObject = {
[LAST_USED_QUERY_ACTION_KEY]: QUERY_ACTIONS.execute,
[ASIDE_HEADER_COMPACT_KEY]: true,
[PARTITIONS_HIDDEN_COLUMNS_KEY]: [],
- [CLUSTER_INFO_HIDDEN_KEY]: true,
[USE_BACKEND_PARAMS_FOR_TABLES_KEY]: false,
[USE_CLUSTER_BALANCER_AS_BACKEND_KEY]: true,
};
diff --git a/src/store/reducers/cluster/cluster.ts b/src/store/reducers/cluster/cluster.ts
index a26334716e..1fcfd2215a 100644
--- a/src/store/reducers/cluster/cluster.ts
+++ b/src/store/reducers/cluster/cluster.ts
@@ -1,12 +1,25 @@
-import type {Reducer} from 'redux';
+import type {Dispatch, Reducer} from 'redux';
+import {DEFAULT_CLUSTER_TAB_KEY} from '../../../utils/constants';
+import {clusterTabsIds, isClusterTab, ClusterTab} from '../../../containers/Cluster/utils';
import {createRequestActionTypes, createApiRequest} from '../../utils';
import type {ClusterAction, ClusterState} from './types';
import {createSelectClusterGroupsQuery, parseGroupsStatsQueryResponse} from './utils';
+const SET_DEFAULT_CLUSTER_TAB = 'cluster/SET_DEFAULT_CLUSTER_TAB';
+
export const FETCH_CLUSTER = createRequestActionTypes('cluster', 'FETCH_CLUSTER');
-const initialState = {loading: true, wasLoaded: false};
+const defaultClusterTabLS = localStorage.getItem(DEFAULT_CLUSTER_TAB_KEY);
+
+let defaultClusterTab;
+if (isClusterTab(defaultClusterTabLS)) {
+ defaultClusterTab = defaultClusterTabLS;
+} else {
+ defaultClusterTab = clusterTabsIds.overview;
+}
+
+const initialState = {loading: true, wasLoaded: false, defaultClusterTab};
const cluster: Reducer = (state = initialState, action) => {
switch (action.type) {
@@ -39,11 +52,33 @@ const cluster: Reducer = (state = initialState, act
loading: false,
};
}
+ case SET_DEFAULT_CLUSTER_TAB: {
+ return {
+ ...state,
+ defaultClusterTab: action.data,
+ };
+ }
default:
return state;
}
};
+export function setDefaultClusterTab(tab: ClusterTab) {
+ return {
+ type: SET_DEFAULT_CLUSTER_TAB,
+ data: tab,
+ } as const;
+}
+
+export function updateDefaultClusterTab(tab: string) {
+ return (dispatch: Dispatch) => {
+ if (isClusterTab(tab)) {
+ localStorage.setItem(DEFAULT_CLUSTER_TAB_KEY, tab);
+ dispatch(setDefaultClusterTab(tab));
+ }
+ };
+}
+
export function getClusterInfo(clusterName?: string) {
async function requestClusterData() {
// Error here is handled by createApiRequest
diff --git a/src/store/reducers/cluster/types.ts b/src/store/reducers/cluster/types.ts
index 9f1ad4428e..e21a944b96 100644
--- a/src/store/reducers/cluster/types.ts
+++ b/src/store/reducers/cluster/types.ts
@@ -1,8 +1,9 @@
+import type {ClusterTab} from '../../../containers/Cluster/utils';
import type {TClusterInfo} from '../../../types/api/cluster';
import type {IResponseError} from '../../../types/api/error';
import type {ApiRequestAction} from '../../utils';
-import {FETCH_CLUSTER} from './cluster';
+import type {FETCH_CLUSTER, setDefaultClusterTab} from './cluster';
export interface DiskErasureGroupsStats {
diskType: string;
@@ -25,6 +26,7 @@ export interface ClusterState {
data?: TClusterInfo;
error?: IResponseError;
groupsStats?: ClusterGroupsStats;
+ defaultClusterTab: ClusterTab;
}
export interface HandledClusterResponse {
@@ -32,8 +34,6 @@ export interface HandledClusterResponse {
groupsStats: ClusterGroupsStats;
}
-export type ClusterAction = ApiRequestAction<
- typeof FETCH_CLUSTER,
- HandledClusterResponse,
- IResponseError
->;
+export type ClusterAction =
+ | ApiRequestAction
+ | ReturnType;
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index cf6f6212c5..c851be7b60 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -98,6 +98,8 @@ export const DEFAULT_IS_TENANT_COMMON_INFO_COLLAPSED = 'default-is-tenant-common
export const DEFAULT_IS_QUERY_RESULT_COLLAPSED = 'default-is-query-result-collapsed';
+export const DEFAULT_CLUSTER_TAB_KEY = 'default-cluster-tab';
+
export const DEFAULT_TABLE_SETTINGS = {
displayIndices: false,
stickyHead: DataTable.MOVING,
@@ -117,8 +119,6 @@ export const LAST_USED_QUERY_ACTION_KEY = 'last_used_query_action';
export const PARTITIONS_HIDDEN_COLUMNS_KEY = 'partitionsHiddenColumns';
-export const CLUSTER_INFO_HIDDEN_KEY = 'clusterInfoHidden';
-
// Remain "tab" in key name for backward compatibility
export const TENANT_INITIAL_PAGE_KEY = 'saved_tenant_initial_tab';