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;