diff --git a/src/containers/Header/Header.scss b/src/containers/Header/Header.scss index 5b3e073f0c..c0af72f01b 100644 --- a/src/containers/Header/Header.scss +++ b/src/containers/Header/Header.scss @@ -10,14 +10,23 @@ border-bottom: 1px solid var(--g-color-line-generic); - &__breadcrumb { + &__breadcrumbs-item { display: flex; - align-items: center; + gap: 3px; + + color: var(--g-color-text-secondary); - &__icon { - display: flex; + &_link:hover { + color: var(--g-color-text-complementary); + } - margin-right: 3px; + &_active { + color: var(--g-color-text-primary); } } + + &__breadcrumbs-icon { + display: flex; + align-items: center; + } } diff --git a/src/containers/Header/Header.tsx b/src/containers/Header/Header.tsx index 359057da7b..0fc1803611 100644 --- a/src/containers/Header/Header.tsx +++ b/src/containers/Header/Header.tsx @@ -1,8 +1,10 @@ import React from 'react'; import {Breadcrumbs} from '@gravity-ui/uikit'; +import {get} from 'lodash'; import {useHistory, useLocation} from 'react-router'; +import {InternalLink} from '../../components/InternalLink'; import {LinkWithIcon} from '../../components/LinkWithIcon/LinkWithIcon'; import {parseQuery} from '../../routes'; import {backend, customBackend} from '../../store'; @@ -33,66 +35,65 @@ interface HeaderProps { function Header({mainPage}: HeaderProps) { const history = useHistory(); const location = useLocation(); + const queryParams = parseQuery(location); const singleClusterMode = useTypedSelector((state) => state.singleClusterMode); const {page, pageBreadcrumbsOptions} = useTypedSelector((state) => state.header); - const queryParams = parseQuery(location); - - const clusterNameFromQuery = queryParams.clusterName?.toString(); - const {currentData: {clusterData: data} = {}} = - clusterApi.useGetClusterInfoQuery(clusterNameFromQuery); + const clusterInfo = clusterApi.useGetClusterInfoQuery(queryParams.clusterName); - const clusterNameFinal = data?.Name || clusterNameFromQuery; + const clusterName = get( + clusterInfo, + ['currentData', 'clusterData', 'Name'], + queryParams.clusterName, + ); const breadcrumbItems = React.useMemo(() => { const rawBreadcrumbs: RawBreadcrumbItem[] = []; - let options = pageBreadcrumbsOptions; + const options = pageBreadcrumbsOptions; if (mainPage) { rawBreadcrumbs.push(mainPage); } - if (clusterNameFinal) { - options = { - ...pageBreadcrumbsOptions, - clusterName: clusterNameFinal, - }; + if (clusterName) { + options.clusterName = clusterName; } const breadcrumbs = getBreadcrumbs(page, options, rawBreadcrumbs, queryParams); return breadcrumbs.map((item) => { - const action = () => { - if (item.link) { - history.push(item.link); - } - }; - return {...item, action}; + return {...item, action: () => {}}; }); - }, [clusterNameFinal, mainPage, history, queryParams, page, pageBreadcrumbsOptions]); + }, [clusterName, mainPage, history, queryParams, page, pageBreadcrumbsOptions]); const renderHeader = () => { return (
-
- { - if (!icon) { - return text; - } - return ( - -
{icon}
- {text} -
- ); - }} - /> -
+ { + const {icon, text, link} = item; + + return ( + + {icon ? ( + {icon} + ) : null} + {text} + + ); + }} + />
diff --git a/src/containers/Header/breadcrumbs.tsx b/src/containers/Header/breadcrumbs.tsx index 5aaa876f78..0ae9d4eda4 100644 --- a/src/containers/Header/breadcrumbs.tsx +++ b/src/containers/Header/breadcrumbs.tsx @@ -40,10 +40,16 @@ export interface RawBreadcrumbItem { icon?: JSX.Element; } -const getClusterBreadcrumbs = ( - options: ClusterBreadcrumbsOptions, - query = {}, -): RawBreadcrumbItem[] => { +interface GetBreadcrumbs { + (options: T, query?: U): RawBreadcrumbItem[]; +} + +const getQueryForTenant = (type: 'nodes' | 'tablets') => ({ + [TENANT_PAGE]: TENANT_PAGES_IDS.diagnostics, + [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS[type], +}); + +const getClusterBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {clusterName, clusterTab} = options; return [ @@ -55,109 +61,97 @@ const getClusterBreadcrumbs = ( ]; }; -const getTenantBreadcrumbs = ( - options: TenantBreadcrumbsOptions, - query = {}, -): RawBreadcrumbItem[] => { +const getTenantBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {tenantName} = options; + const breadcrumbs = getClusterBreadcrumbs(options, query); + const text = tenantName ? prepareTenantName(tenantName) : headerKeyset('breadcrumbs.tenant'); const link = tenantName ? getTenantPath({...query, name: tenantName}) : undefined; - return [...getClusterBreadcrumbs(options, query), {text, link, icon: }]; + const lastItem = {text, link, icon: }; + breadcrumbs.push(lastItem); + + return breadcrumbs; }; -const getNodeBreadcrumbs = (options: NodeBreadcrumbsOptions, query = {}): RawBreadcrumbItem[] => { +const getNodeBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {tenantName, nodeId} = options; - - let breadcrumbs: RawBreadcrumbItem[]; - // Compute nodes have tenantName, storage nodes doesn't - const isStorageNode = !tenantName; + const isStorage = !tenantName; - const newQuery = { - ...query, - [TENANT_PAGE]: TENANT_PAGES_IDS.diagnostics, - [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes, - }; + const tenantQuery = getQueryForTenant('nodes'); - if (isStorageNode) { - breadcrumbs = getClusterBreadcrumbs(options, query); - } else { - breadcrumbs = getTenantBreadcrumbs(options, newQuery); - } + const breadcrumbs = isStorage + ? getClusterBreadcrumbs(options, query) + : getTenantBreadcrumbs(options, {...query, ...tenantQuery}); - const text = nodeId - ? `${headerKeyset('breadcrumbs.node')} ${nodeId}` - : headerKeyset('breadcrumbs.node'); - const link = nodeId ? getDefaultNodePath(nodeId, query) : undefined; - const icon = isStorageNode ? : ; + let text = headerKeyset('breadcrumbs.node'); + if (nodeId) { + text += ` ${nodeId}`; + } - breadcrumbs.push({ + const lastItem = { text, - link, - icon, - }); + link: nodeId ? getDefaultNodePath(nodeId, query) : undefined, + icon: isStorage ? : , + }; + + breadcrumbs.push(lastItem); return breadcrumbs; }; -const getPDiskBreadcrumbs = (options: PDiskBreadcrumbsOptions, query = {}) => { +const getPDiskBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {nodeId, pDiskId} = options; const breadcrumbs = getNodeBreadcrumbs({ - // PDisks relate to storage Nodes, they don't have tenant name - tenantName: undefined, - nodeId: nodeId, + nodeId, }); - const text = pDiskId - ? `${headerKeyset('breadcrumbs.pDisk')} ${pDiskId}` - : headerKeyset('breadcrumbs.pDisk'); - const link = pDiskId && nodeId ? getPDiskPagePath(pDiskId, nodeId, query) : undefined; + let text = headerKeyset('breadcrumbs.pDisk'); + if (pDiskId) { + text += ` ${pDiskId}`; + } + + const hasLink = pDiskId && nodeId; + const link = hasLink ? getPDiskPagePath(pDiskId, nodeId, query) : undefined; - breadcrumbs.push({ + const lastItem = { text, link, - }); + }; + breadcrumbs.push(lastItem); return breadcrumbs; }; -const getVDiskBreadcrumbs = (options: VDiskBreadcrumbsOptions, query = {}) => { +const getVDiskBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {vDiskSlotId} = options; const breadcrumbs = getPDiskBreadcrumbs(options, query); - const text = vDiskSlotId - ? `${headerKeyset('breadcrumbs.vDisk')} ${vDiskSlotId}` - : headerKeyset('breadcrumbs.vDisk'); + let text = headerKeyset('breadcrumbs.vDisk'); + if (vDiskSlotId) { + text += ` ${vDiskSlotId}`; + } - breadcrumbs.push({text}); + const lastItem = { + text, + }; + breadcrumbs.push(lastItem); return breadcrumbs; }; -const getTabletsBreadcrubms = ( - options: TabletsBreadcrumbsOptions, - query = {}, -): RawBreadcrumbItem[] => { +const getTabletsBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {tenantName, nodeIds} = options; - const newQuery = { - ...query, - [TENANT_PAGE]: TENANT_PAGES_IDS.diagnostics, - [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.tablets, - }; + const tenantQuery = getQueryForTenant('tablets'); - let breadcrumbs: RawBreadcrumbItem[]; - - // Cluster system tablets don't have tenantName - if (tenantName) { - breadcrumbs = getTenantBreadcrumbs(options, newQuery); - } else { - breadcrumbs = getClusterBreadcrumbs(options, query); - } + const breadcrumbs = tenantName + ? getTenantBreadcrumbs(options, {...query, ...tenantQuery}) + : getClusterBreadcrumbs(options, query); const link = createHref(routes.tabletsFilters, undefined, { ...query, @@ -165,57 +159,50 @@ const getTabletsBreadcrubms = ( path: tenantName, }); - breadcrumbs.push({text: headerKeyset('breadcrumbs.tablets'), link}); + const lastItem = {text: headerKeyset('breadcrumbs.tablets'), link}; + breadcrumbs.push(lastItem); return breadcrumbs; }; -const getTabletBreadcrubms = ( - options: TabletBreadcrumbsOptions, - query = {}, -): RawBreadcrumbItem[] => { +const getTabletBreadcrumbs: GetBreadcrumbs = (options, query = {}) => { const {tabletId, tabletType} = options; - const breadcrumbs = getTabletsBreadcrubms(options, query); + const breadcrumbs = getTabletsBreadcrumbs(options, query); - breadcrumbs.push({ + const lastItem = { text: tabletId || headerKeyset('breadcrumbs.tablet'), icon: , - }); + }; + + breadcrumbs.push(lastItem); return breadcrumbs; }; +const mapPageToGetter = { + cluster: getClusterBreadcrumbs, + node: getNodeBreadcrumbs, + pDisk: getPDiskBreadcrumbs, + tablet: getTabletBreadcrumbs, + tablets: getTabletsBreadcrumbs, + tenant: getTenantBreadcrumbs, + vDisk: getVDiskBreadcrumbs, +} as const; + export const getBreadcrumbs = ( page: Page, options: BreadcrumbsOptions, rawBreadcrumbs: RawBreadcrumbItem[] = [], query = {}, ) => { - switch (page) { - case 'cluster': { - return [...rawBreadcrumbs, ...getClusterBreadcrumbs(options, query)]; - } - case 'tenant': { - return [...rawBreadcrumbs, ...getTenantBreadcrumbs(options, query)]; - } - case 'node': { - return [...rawBreadcrumbs, ...getNodeBreadcrumbs(options, query)]; - } - case 'pDisk': { - return [...rawBreadcrumbs, ...getPDiskBreadcrumbs(options, query)]; - } - case 'vDisk': { - return [...rawBreadcrumbs, ...getVDiskBreadcrumbs(options, query)]; - } - case 'tablets': { - return [...rawBreadcrumbs, ...getTabletsBreadcrubms(options, query)]; - } - case 'tablet': { - return [...rawBreadcrumbs, ...getTabletBreadcrubms(options, query)]; - } - default: { - return rawBreadcrumbs; - } + if (!page) { + return rawBreadcrumbs; } + + const getter = mapPageToGetter[page]; + + const pageBreadcrumbs = getter(options, query); + + return [...rawBreadcrumbs, ...pageBreadcrumbs]; }; diff --git a/src/routes.ts b/src/routes.ts index a2d3e46b24..6a50d66e25 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -50,10 +50,7 @@ const prepareRoute = (route: string) => { return preparedRoute; }; -export type Query = Record< - string | number, - string | number | string[] | number[] | undefined | null ->; +export type Query = AnyRecord; export function createHref( route: string, diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000000..fa4d2d94ac --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1 @@ +declare type AnyRecord = Record; diff --git a/src/utils/utils.js b/src/utils/utils.js index 7d80784e15..d893a3a1cf 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -25,7 +25,9 @@ export function bytesToSize(bytes) { if (isNaN(bytes)) { return ''; } - if (bytes < base) return String(bytes); + if (bytes < base) { + return String(bytes); + } let i = parseInt(Math.floor(Math.log(bytes) / Math.log(base)), 10); if (i >= sizes.length) { i = sizes.length - 1;