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