mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cloud Posture] Implement breadcrumbs for security solution (#136821)
This commit is contained in:
parent
770802b1cb
commit
b61625cec6
13 changed files with 91 additions and 63 deletions
|
@ -26,3 +26,8 @@ export type CloudSecurityPosturePageId =
|
|||
| 'cloud_security_posture-findings'
|
||||
| 'cloud_security_posture-benchmarks'
|
||||
| 'cloud_security_posture-rules';
|
||||
|
||||
export interface BreadcrumbEntry {
|
||||
readonly name: string;
|
||||
readonly path: string;
|
||||
}
|
||||
|
|
|
@ -4,21 +4,17 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { MouseEvent } from 'react';
|
||||
import type { ChromeBreadcrumb, CoreStart } from '@kbn/core/public';
|
||||
import { useEffect } from 'react';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { type RouteProps, useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import { type RouteProps, useRouteMatch } from 'react-router-dom';
|
||||
import type { EuiBreadcrumb } from '@elastic/eui';
|
||||
import { string } from 'io-ts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CLOUD_SECURITY_POSTURE_BASE_PATH } from '../..';
|
||||
import type { CspNavigationItem } from './types';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import type { BreadcrumbEntry } from './types';
|
||||
|
||||
const getClickableBreadcrumb = (
|
||||
routeMatch: RouteProps['path'],
|
||||
breadcrumbPath: CspNavigationItem['path']
|
||||
) => {
|
||||
const getClickableBreadcrumb = (routeMatch: RouteProps['path'], breadcrumbPath: string) => {
|
||||
const hasParams = breadcrumbPath.includes(':');
|
||||
if (hasParams) return;
|
||||
|
||||
|
@ -27,15 +23,20 @@ const getClickableBreadcrumb = (
|
|||
}
|
||||
};
|
||||
|
||||
export const useCspBreadcrumbs = (breadcrumbs: CspNavigationItem[]) => {
|
||||
export const useCspBreadcrumbs = (breadcrumbs: BreadcrumbEntry[]) => {
|
||||
const {
|
||||
services: {
|
||||
chrome: { setBreadcrumbs, docTitle },
|
||||
application: { getUrlForApp },
|
||||
application: { currentAppId$, applications$, navigateToApp },
|
||||
},
|
||||
} = useKibana<CoreStart>();
|
||||
|
||||
const match = useRouteMatch();
|
||||
const history = useHistory();
|
||||
|
||||
const appId = useObservable(currentAppId$);
|
||||
const applications = useObservable(applications$);
|
||||
const application = appId ? applications?.get(appId) : undefined;
|
||||
const appTitle = application?.title;
|
||||
|
||||
useEffect(() => {
|
||||
const additionalBreadCrumbs: ChromeBreadcrumb[] = breadcrumbs.map((breadcrumb) => {
|
||||
|
@ -43,31 +44,32 @@ export const useCspBreadcrumbs = (breadcrumbs: CspNavigationItem[]) => {
|
|||
|
||||
return {
|
||||
text: breadcrumb.name,
|
||||
...(clickableLink && {
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
history.push(clickableLink);
|
||||
},
|
||||
}),
|
||||
...(clickableLink &&
|
||||
appId && {
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
void navigateToApp(appId, { path: clickableLink });
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const nextBreadcrumbs = [
|
||||
{
|
||||
text: i18n.translate('xpack.csp.navigation.cloudPostureBreadcrumbLabel', {
|
||||
defaultMessage: 'Cloud Posture',
|
||||
text: appTitle,
|
||||
...(appId && {
|
||||
onClick: (e: MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault();
|
||||
void navigateToApp(appId);
|
||||
},
|
||||
}),
|
||||
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault();
|
||||
history.push(CLOUD_SECURITY_POSTURE_BASE_PATH);
|
||||
},
|
||||
},
|
||||
...additionalBreadCrumbs,
|
||||
];
|
||||
|
||||
setBreadcrumbs(nextBreadcrumbs);
|
||||
docTitle.change(getTextBreadcrumbs(nextBreadcrumbs));
|
||||
}, [match.path, getUrlForApp, setBreadcrumbs, breadcrumbs, history, docTitle]);
|
||||
}, [match.path, setBreadcrumbs, breadcrumbs, docTitle, appTitle, appId, navigateToApp]);
|
||||
};
|
||||
|
||||
const getTextBreadcrumbs = (breadcrumbs: EuiBreadcrumb[]) =>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React from 'react';
|
||||
import type { UseQueryResult } from 'react-query';
|
||||
import { Redirect, Switch, Route, useLocation } from 'react-router-dom';
|
||||
import { useCspBreadcrumbs } from '../../common/navigation/use_csp_breadcrumbs';
|
||||
import { CloudPosturePage } from '../../components/cloud_posture_page';
|
||||
import { useFindingsEsPit } from './es_pit/use_findings_es_pit';
|
||||
import { FindingsEsPitContext } from './es_pit/findings_es_pit_context';
|
||||
|
@ -68,8 +69,14 @@ export const FindingsNoPageTemplate = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export const Findings = () => (
|
||||
<CspPageTemplate paddingSize="none">
|
||||
<FindingsNoPageTemplate />
|
||||
</CspPageTemplate>
|
||||
);
|
||||
const FINDINGS_BREADCRUMBS = [cloudPosturePages.findings];
|
||||
|
||||
export const Findings = () => {
|
||||
useCspBreadcrumbs(FINDINGS_BREADCRUMBS);
|
||||
|
||||
return (
|
||||
<CspPageTemplate paddingSize="none">
|
||||
<FindingsNoPageTemplate />
|
||||
</CspPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -26,8 +26,6 @@ import {
|
|||
} from '../utils';
|
||||
import { PageWrapper, PageTitle, PageTitleText } from '../layout/findings_layout';
|
||||
import { FindingsGroupBySelector } from '../layout/findings_group_by_selector';
|
||||
import { useCspBreadcrumbs } from '../../../common/navigation/use_csp_breadcrumbs';
|
||||
import { findingsNavigation } from '../../../common/navigation/constants';
|
||||
import { useUrlQuery } from '../../../common/hooks/use_url_query';
|
||||
import { ErrorCallout } from '../layout/error_callout';
|
||||
|
||||
|
@ -43,8 +41,6 @@ export const getDefaultQuery = ({
|
|||
});
|
||||
|
||||
export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => {
|
||||
useCspBreadcrumbs([findingsNavigation.findings_default]);
|
||||
|
||||
const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery);
|
||||
const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery);
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ import {
|
|||
import { PageTitle, PageTitleText, PageWrapper } from '../layout/findings_layout';
|
||||
import { FindingsGroupBySelector } from '../layout/findings_group_by_selector';
|
||||
import { findingsNavigation } from '../../../common/navigation/constants';
|
||||
import { useCspBreadcrumbs } from '../../../common/navigation/use_csp_breadcrumbs';
|
||||
import { ResourceFindings } from './resource_findings/resource_findings_container';
|
||||
import { ErrorCallout } from '../layout/error_callout';
|
||||
import { FindingsDistributionBar } from '../layout/findings_distribution_bar';
|
||||
|
@ -57,8 +56,6 @@ export const FindingsByResourceContainer = ({ dataView }: FindingsBaseProps) =>
|
|||
);
|
||||
|
||||
const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => {
|
||||
useCspBreadcrumbs([findingsNavigation.findings_by_resource]);
|
||||
|
||||
const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery);
|
||||
const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery);
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import { generatePath } from 'react-router-dom';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import * as TEST_SUBJECTS from '../../test_subjects';
|
||||
import { PageWrapper, PageTitle, PageTitleText } from '../../layout/findings_layout';
|
||||
import { useCspBreadcrumbs } from '../../../../common/navigation/use_csp_breadcrumbs';
|
||||
import { findingsNavigation } from '../../../../common/navigation/constants';
|
||||
import { ResourceFindingsQuery, useResourceFindings } from './use_resource_findings';
|
||||
import { useUrlQuery } from '../../../../common/hooks/use_url_query';
|
||||
|
@ -54,7 +53,6 @@ const BackToResourcesButton = () => (
|
|||
);
|
||||
|
||||
export const ResourceFindings = ({ dataView }: FindingsBaseProps) => {
|
||||
useCspBreadcrumbs([findingsNavigation.findings_default]);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const params = useParams<{ resourceId: string }>();
|
||||
|
||||
|
|
|
@ -5,31 +5,56 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import { generatePath, Link, type RouteComponentProps } from 'react-router-dom';
|
||||
import { EuiTextColor, EuiButtonEmpty, EuiFlexGroup, EuiPageHeader, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { pagePathGetters } from '@kbn/fleet-plugin/public';
|
||||
import type { BreadcrumbEntry } from '../../common/navigation/types';
|
||||
import { RulesContainer, type PageUrlParams } from './rules_container';
|
||||
import { cloudPosturePages } from '../../common/navigation/constants';
|
||||
import { useCspBreadcrumbs } from '../../common/navigation/use_csp_breadcrumbs';
|
||||
import type { CspPageNavigationItem } from '../../common/navigation/types';
|
||||
import { useCspIntegrationInfo } from './use_csp_integration';
|
||||
import { CspPageTemplate } from '../../components/csp_page_template';
|
||||
import { useKibana } from '../../common/hooks/use_kibana';
|
||||
import { CloudPosturePage } from '../../components/cloud_posture_page';
|
||||
import { SecuritySolutionContext } from '../../application/security_solution_context';
|
||||
|
||||
const getRulesBreadcrumbs = (name?: string): CspPageNavigationItem[] =>
|
||||
[cloudPosturePages.benchmarks, { ...cloudPosturePages.rules, name }].filter(
|
||||
(breadcrumb): breadcrumb is CspPageNavigationItem => !!breadcrumb.name
|
||||
);
|
||||
const getRulesBreadcrumbs = (
|
||||
name?: string,
|
||||
manageBreadcrumb?: BreadcrumbEntry
|
||||
): BreadcrumbEntry[] => {
|
||||
const breadCrumbs: BreadcrumbEntry[] = [];
|
||||
if (manageBreadcrumb) {
|
||||
breadCrumbs.push(manageBreadcrumb);
|
||||
}
|
||||
|
||||
breadCrumbs.push(cloudPosturePages.benchmarks);
|
||||
|
||||
if (name) {
|
||||
breadCrumbs.push({ ...cloudPosturePages.rules, name });
|
||||
} else {
|
||||
breadCrumbs.push(cloudPosturePages.rules);
|
||||
}
|
||||
|
||||
return breadCrumbs;
|
||||
};
|
||||
|
||||
export const RulesNoPageTemplate = ({ match: { params } }: RouteComponentProps<PageUrlParams>) => {
|
||||
const { http } = useKibana().services;
|
||||
const integrationInfo = useCspIntegrationInfo(params);
|
||||
const securitySolutionContext = useContext(SecuritySolutionContext);
|
||||
|
||||
const [packageInfo, agentInfo] = integrationInfo.data || [];
|
||||
|
||||
const breadcrumbs = useMemo(
|
||||
() =>
|
||||
getRulesBreadcrumbs(packageInfo?.name, securitySolutionContext?.getManageBreadcrumbEntry()),
|
||||
[packageInfo?.name, securitySolutionContext]
|
||||
);
|
||||
|
||||
useCspBreadcrumbs(breadcrumbs);
|
||||
|
||||
return (
|
||||
<CloudPosturePage query={integrationInfo}>
|
||||
<EuiPageHeader
|
||||
|
@ -89,19 +114,6 @@ export const RulesNoPageTemplate = ({ match: { params } }: RouteComponentProps<P
|
|||
};
|
||||
|
||||
export const Rules = (props: RouteComponentProps<PageUrlParams>) => {
|
||||
const { params } = props.match;
|
||||
const integrationInfo = useCspIntegrationInfo(params);
|
||||
|
||||
const [packageInfo] = integrationInfo.data || [];
|
||||
|
||||
const breadcrumbs = useMemo(
|
||||
// TODO: make benchmark breadcrumb navigable
|
||||
() => getRulesBreadcrumbs(packageInfo?.name),
|
||||
[packageInfo?.name]
|
||||
);
|
||||
|
||||
useCspBreadcrumbs(breadcrumbs);
|
||||
|
||||
return (
|
||||
<CspPageTemplate>
|
||||
<RulesNoPageTemplate {...props} />
|
||||
|
|
|
@ -10,7 +10,7 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/
|
|||
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import type { DiscoverStart } from '@kbn/discover-plugin/public';
|
||||
import type { CloudSecurityPosturePageId } from './common/navigation/types';
|
||||
import type { BreadcrumbEntry, CloudSecurityPosturePageId } from './common/navigation/types';
|
||||
|
||||
/**
|
||||
* The cloud security posture's public plugin setup interface.
|
||||
|
@ -52,4 +52,6 @@ export interface CspSecuritySolutionContext {
|
|||
getFiltersGlobalComponent: () => ComponentType<{ children: ReactNode }>;
|
||||
/** Gets the `SpyRoute` component for navigation highlighting and breadcrumbs. */
|
||||
getSpyRouteComponent: () => ComponentType<{ pageName?: CloudSecurityPosturePageId }>;
|
||||
/** Gets the `Manage` breadcrumb entry. */
|
||||
getManageBreadcrumbEntry: () => BreadcrumbEntry;
|
||||
}
|
||||
|
|
|
@ -12,11 +12,13 @@ import {
|
|||
type CspSecuritySolutionContext,
|
||||
} from '@kbn/cloud-security-posture-plugin/public';
|
||||
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
|
||||
import { MANAGE_PATH } from '../../common/constants';
|
||||
import type { SecurityPageName, SecuritySubPluginRoutes } from '../app/types';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper';
|
||||
import { SpyRoute } from '../common/utils/route/spy_routes';
|
||||
import { FiltersGlobal } from '../common/components/filters_global';
|
||||
import { MANAGE } from '../app/translations';
|
||||
|
||||
// This exists only for the type signature cast
|
||||
const CloudPostureSpyRoute = ({ pageName }: { pageName?: CloudSecurityPosturePageId }) => (
|
||||
|
@ -29,6 +31,7 @@ const CloudSecurityPosture = memo(() => {
|
|||
const securitySolutionContext: CspSecuritySolutionContext = {
|
||||
getFiltersGlobalComponent: () => FiltersGlobal,
|
||||
getSpyRouteComponent: () => CloudPostureSpyRoute,
|
||||
getManageBreadcrumbEntry: () => ({ name: MANAGE, path: MANAGE_PATH }),
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -80,7 +80,13 @@ export const getBreadcrumbsForRoute = (
|
|||
): ChromeBreadcrumb[] | null => {
|
||||
const spyState: RouteSpyState = omit('navTabs', object);
|
||||
|
||||
if (!spyState || !object.navTabs || !spyState.pageName || isCaseRoutes(spyState)) {
|
||||
if (
|
||||
!spyState ||
|
||||
!object.navTabs ||
|
||||
!spyState.pageName ||
|
||||
isCaseRoutes(spyState) ||
|
||||
isCloudSecurityPostureManagedRoutes(spyState)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -164,6 +170,9 @@ const isRulesRoutes = (spyState: RouteSpyState): spyState is AdministrationRoute
|
|||
spyState.pageName === SecurityPageName.rules ||
|
||||
spyState.pageName === SecurityPageName.rulesCreate;
|
||||
|
||||
const isCloudSecurityPostureManagedRoutes = (spyState: RouteSpyState) =>
|
||||
spyState.pageName === SecurityPageName.cloudSecurityPostureRules;
|
||||
|
||||
const emptyLastBreadcrumbUrl = (breadcrumbs: ChromeBreadcrumb[]) => {
|
||||
const leadingBreadCrumbs = breadcrumbs.slice(0, -1);
|
||||
const lastBreadcrumb = last(breadcrumbs);
|
||||
|
|
|
@ -10429,7 +10429,6 @@
|
|||
"xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "Ressource",
|
||||
"xpack.csp.findings.resourceFindings.backToResourcesPageButtonLabel": "Retour à la vue de regroupement par ressource",
|
||||
"xpack.csp.findings.searchBar.searchPlaceholder": "Rechercher dans les résultats (par ex. rule.section.keyword : \"serveur d'API\")",
|
||||
"xpack.csp.navigation.cloudPostureBreadcrumbLabel": "Niveau du cloud",
|
||||
"xpack.csp.rules.activateAllButtonLabel": "Activer {count, plural, one {# règle} other {# règles}}",
|
||||
"xpack.csp.rules.clearSelectionButtonLabel": "Effacer la sélection",
|
||||
"xpack.csp.rules.deactivateAllButtonLabel": "Désactiver {count, plural, one {# règle} other {# règles}}",
|
||||
|
|
|
@ -10421,7 +10421,6 @@
|
|||
"xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "リソース",
|
||||
"xpack.csp.findings.resourceFindings.backToResourcesPageButtonLabel": "リソース別グループビューに戻る",
|
||||
"xpack.csp.findings.searchBar.searchPlaceholder": "検索結果(例:rule.section.keyword:\"API Server\")",
|
||||
"xpack.csp.navigation.cloudPostureBreadcrumbLabel": "クラウド態勢",
|
||||
"xpack.csp.rules.activateAllButtonLabel": "{count, plural, other {#個のルール}}をアクティブ化",
|
||||
"xpack.csp.rules.clearSelectionButtonLabel": "選択した項目をクリア",
|
||||
"xpack.csp.rules.deactivateAllButtonLabel": "{count, plural, other {#個のルール}}を非アクティブ化",
|
||||
|
|
|
@ -10436,7 +10436,6 @@
|
|||
"xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "资源",
|
||||
"xpack.csp.findings.resourceFindings.backToResourcesPageButtonLabel": "返回到按资源视图分组",
|
||||
"xpack.csp.findings.searchBar.searchPlaceholder": "搜索结果(例如,rule.section.keyword:“APM 服务器”)",
|
||||
"xpack.csp.navigation.cloudPostureBreadcrumbLabel": "云态势",
|
||||
"xpack.csp.rules.activateAllButtonLabel": "激活 {count, plural, other {# 个规则}}",
|
||||
"xpack.csp.rules.clearSelectionButtonLabel": "清除所选内容",
|
||||
"xpack.csp.rules.deactivateAllButtonLabel": "停用 {count, plural, other {# 个规则}}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue