From 57c349277f4831fb405bc4261cc956b127d9623a Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Fri, 9 Feb 2024 12:38:35 +0000 Subject: [PATCH 1/3] feat: move cluster info to overview tab Closes #623 --- src/containers/Cluster/Cluster.scss | 15 +++++++ src/containers/Cluster/Cluster.tsx | 40 ++++++++++++----- .../Cluster/ClusterInfo/ClusterInfo.scss | 40 ----------------- .../Cluster/ClusterInfo/ClusterInfo.tsx | 44 ++----------------- src/containers/Cluster/utils.tsx | 8 +++- src/services/settings.ts | 2 - src/utils/constants.ts | 2 - 7 files changed, 55 insertions(+), 96 deletions(-) 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..8a20a3bd3f 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -4,7 +4,7 @@ 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, @@ -25,6 +25,8 @@ 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'; @@ -103,6 +105,18 @@ function Cluster({ const renderTab = () => { switch (activeTab) { + case clusterTabsIds.overview: { + return ( + + ); + } case clusterTabsIds.tenants: { return ; } @@ -130,18 +144,24 @@ function Cluster({ } } }; + const getClusterTitle = () => { + if (infoLoading) { + return ; + } + + return ( + + ); + }; return (
- - +
{getClusterTitle()}
{ 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..70d12f9557 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,7 +33,7 @@ const versions = { title: 'Versions', }; -export const clusterTabs = [tenants, nodes, storage, versions]; +export const clusterTabs = [overview, tenants, nodes, storage, versions]; export const getClusterPath = (activeTab: ClusterTab = clusterTabsIds.tenants, query = {}) => { return createHref(routes.cluster, {activeTab}, query); 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/utils/constants.ts b/src/utils/constants.ts index cf6f6212c5..e84ef9037f 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -117,8 +117,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'; From 8aea296b67d46e492f8c22298db7b990d3205ac5 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Tue, 13 Feb 2024 12:28:51 +0000 Subject: [PATCH 2/3] feat(Cluster): remember last visited tab --- src/containers/App/Content.tsx | 13 +-- src/containers/Cluster/Cluster.tsx | 141 +++++++++++++++++--------- src/containers/Cluster/utils.tsx | 8 +- src/containers/Clusters/columns.tsx | 2 +- src/containers/Node/Node.tsx | 3 - src/routes.ts | 5 + src/store/reducers/cluster/cluster.ts | 39 ++++++- src/store/reducers/cluster/types.ts | 12 +-- src/utils/constants.ts | 2 + 9 files changed, 151 insertions(+), 74 deletions(-) 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.tsx b/src/containers/Cluster/Cluster.tsx index 8a20a3bd3f..af76d18e9b 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -1,5 +1,5 @@ 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'; @@ -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'; @@ -29,7 +29,7 @@ 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'; @@ -52,8 +52,7 @@ function Cluster({ const dispatch = useDispatch(); - const match = useRouteMatch<{activeTab: string}>(routes.cluster); - const {activeTab = clusterTabsIds.tenants} = match?.params || {}; + const {activeTab, defaultTab} = useClusterTab(); const location = useLocation(); const queryParams = qs.parse(location.search, { @@ -103,47 +102,6 @@ function Cluster({ return parseNodesToVersionsValues(nodes, versionToColor); }, [nodes, versionToColor]); - const renderTab = () => { - switch (activeTab) { - case clusterTabsIds.overview: { - return ( - - ); - } - case clusterTabsIds.tenants: { - return ; - } - case clusterTabsIds.nodes: { - return ( - - ); - } - case clusterTabsIds.storage: { - return ( - - ); - } - case clusterTabsIds.versions: { - return ; - } - default: { - return null; - } - } - }; const getClusterTitle = () => { if (infoLoading) { return ; @@ -166,12 +124,18 @@ function Cluster({ { const path = getClusterPath(id as ClusterTab, queryParams); return ( - + { + dispatch(updateDefaultClusterTab(id)); + }} + > {node} ); @@ -179,9 +143,86 @@ function Cluster({ />
-
{renderTab()}
+
+ + + + + + + + + + + + + + + + + + +
); } +function useClusterTab() { + const dispatch = useDispatch(); + + const defaultTab = useTypedSelector((state) => state.cluster.defaultClusterTab); + + const match = useRouteMatch<{activeTab: string}>(routes.cluster); + + let {activeTab = defaultTab} = match?.params || {}; + if (!isClusterTab(activeTab)) { + activeTab = defaultTab; + } + + useEffect(() => { + if (activeTab !== defaultTab) { + dispatch(updateDefaultClusterTab(activeTab)); + } + }, [activeTab, defaultTab, dispatch]); + + return {activeTab, defaultTab}; +} + export default Cluster; diff --git a/src/containers/Cluster/utils.tsx b/src/containers/Cluster/utils.tsx index 70d12f9557..3ea4c3816c 100644 --- a/src/containers/Cluster/utils.tsx +++ b/src/containers/Cluster/utils.tsx @@ -35,6 +35,10 @@ const versions = { export const clusterTabs = [overview, tenants, nodes, storage, versions]; -export const getClusterPath = (activeTab: ClusterTab = clusterTabsIds.tenants, query = {}) => { - return createHref(routes.cluster, {activeTab}, query); +export function isClusterTab(tab: any): tab is ClusterTab { + return Object.values(clusterTabsIds).includes(tab); +} + +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/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 e84ef9037f..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, From 0100734ab779cbb63708cda51b14ada17afe670d Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Wed, 14 Feb 2024 13:24:45 +0100 Subject: [PATCH 3/3] fix: return only active tab from useClusterTab hook --- src/containers/Cluster/Cluster.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx index af76d18e9b..c225fb8ec1 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -52,7 +52,7 @@ function Cluster({ const dispatch = useDispatch(); - const {activeTab, defaultTab} = useClusterTab(); + const activeTab = useClusterTab(); const location = useLocation(); const queryParams = qs.parse(location.search, { @@ -197,7 +197,7 @@ function Cluster({ > - + @@ -211,8 +211,11 @@ function useClusterTab() { const match = useRouteMatch<{activeTab: string}>(routes.cluster); - let {activeTab = defaultTab} = match?.params || {}; - if (!isClusterTab(activeTab)) { + const {activeTab: activeTabFromParams} = match?.params || {}; + let activeTab: ClusterTab; + if (isClusterTab(activeTabFromParams)) { + activeTab = activeTabFromParams; + } else { activeTab = defaultTab; } @@ -222,7 +225,7 @@ function useClusterTab() { } }, [activeTab, defaultTab, dispatch]); - return {activeTab, defaultTab}; + return activeTab; } export default Cluster;