mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Cloud Posture] Fully integrate cloud posture pages into security solution (#137058)
This commit is contained in:
parent
58f7eaf0f8
commit
0be138c0a9
49 changed files with 545 additions and 942 deletions
|
@ -133,7 +133,6 @@ export const applicationUsageSchema = {
|
|||
// X-Pack
|
||||
apm: commonSchema,
|
||||
canvas: commonSchema,
|
||||
csp: commonSchema,
|
||||
enterpriseSearch: commonSchema,
|
||||
enterpriseSearchContent: commonSchema,
|
||||
elasticsearch: commonSchema,
|
||||
|
|
|
@ -2092,137 +2092,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"csp": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"enterpriseSearch": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
|
|
|
@ -36,7 +36,6 @@ export const RULE_FAILED = `failed`;
|
|||
// A mapping of in-development features to their status. These features should be hidden from users but can be easily
|
||||
// activated via a simple code change in a single location.
|
||||
export const INTERNAL_FEATURE_FLAGS = {
|
||||
showBenchmarks: true,
|
||||
showManageRulesMock: false,
|
||||
showFindingsGroupBy: true,
|
||||
} as const;
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { APP_WRAPPER_CLASS, type AppMountParameters, type CoreStart } from '@kbn/core/public';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { KibanaContextProvider, RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
|
||||
import React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { cloudPosturePages } from '../common/navigation/constants';
|
||||
import type { CspClientPluginStartDeps } from '../types';
|
||||
import { pageToComponentMapping } from './constants';
|
||||
import { CspRouter, getRoutesFromMapping } from './csp_router';
|
||||
|
||||
export interface CspAppDeps {
|
||||
core: CoreStart;
|
||||
deps: CspClientPluginStartDeps;
|
||||
params: AppMountParameters;
|
||||
}
|
||||
|
||||
const cspPluginRoutes = getRoutesFromMapping(cloudPosturePages, pageToComponentMapping);
|
||||
|
||||
export const CspApp = ({ core, deps, params }: CspAppDeps) => (
|
||||
<RedirectAppLinks application={core.application} className={APP_WRAPPER_CLASS}>
|
||||
<KibanaContextProvider services={{ ...deps, ...core }}>
|
||||
<EuiErrorBoundary>
|
||||
<Router history={params.history}>
|
||||
<I18nProvider>
|
||||
<CspRouter routes={cspPluginRoutes} />
|
||||
</I18nProvider>
|
||||
</Router>
|
||||
</EuiErrorBoundary>
|
||||
</KibanaContextProvider>
|
||||
</RedirectAppLinks>
|
||||
);
|
|
@ -14,10 +14,3 @@ export const pageToComponentMapping: Record<CspPage, RouteProps['component']> =
|
|||
benchmarks: pages.Benchmarks,
|
||||
rules: pages.Rules,
|
||||
};
|
||||
|
||||
export const pageToComponentMappingNoPageTemplate: Record<CspPage, RouteProps['component']> = {
|
||||
findings: pages.FindingsNoPageTemplate,
|
||||
dashboard: pages.ComplianceDashboardNoPageTemplate,
|
||||
benchmarks: pages.BenchmarksNoPageTemplate,
|
||||
rules: pages.RulesNoPageTemplate,
|
||||
};
|
||||
|
|
|
@ -11,8 +11,7 @@ import { Redirect, Route, RouteComponentProps, type RouteProps, Switch } from 'r
|
|||
import { CLOUD_SECURITY_POSTURE_BASE_PATH, type CspSecuritySolutionContext } from '..';
|
||||
import { cloudPosturePages } from '../common/navigation/constants';
|
||||
import type { CloudSecurityPosturePageId, CspPageNavigationItem } from '../common/navigation/types';
|
||||
import { UnknownRoute } from '../components/unknown_route';
|
||||
import { pageToComponentMappingNoPageTemplate } from './constants';
|
||||
import { pageToComponentMapping } from './constants';
|
||||
import { SecuritySolutionContext } from './security_solution_context';
|
||||
|
||||
type CspRouteProps = RouteProps & {
|
||||
|
@ -60,20 +59,14 @@ export const addSpyRouteComponentToRoute = (
|
|||
return newRoute;
|
||||
};
|
||||
|
||||
const securitySolutionRoutes = getRoutesFromMapping(
|
||||
cloudPosturePages,
|
||||
pageToComponentMappingNoPageTemplate
|
||||
);
|
||||
const securitySolutionRoutes = getRoutesFromMapping(cloudPosturePages, pageToComponentMapping);
|
||||
|
||||
/** Props for the cloud security posture router component */
|
||||
export interface CspRouterProps {
|
||||
routes?: readonly CspRouteProps[];
|
||||
securitySolutionContext?: CspSecuritySolutionContext;
|
||||
}
|
||||
|
||||
export const CspRouter = ({
|
||||
routes = securitySolutionRoutes,
|
||||
securitySolutionContext,
|
||||
}: CspRouterProps) => {
|
||||
export const CspRouter = ({ securitySolutionContext }: CspRouterProps) => {
|
||||
const SpyRoute = securitySolutionContext
|
||||
? securitySolutionContext.getSpyRouteComponent()
|
||||
: undefined;
|
||||
|
@ -81,12 +74,11 @@ export const CspRouter = ({
|
|||
const routerElement = (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Switch>
|
||||
{routes.map((route) => {
|
||||
{securitySolutionRoutes.map((route) => {
|
||||
const routeProps = SpyRoute ? addSpyRouteComponentToRoute(route, SpyRoute) : route;
|
||||
return <Route key={routeProps.path} {...routeProps} />;
|
||||
})}
|
||||
<Route exact path={CLOUD_SECURITY_POSTURE_BASE_PATH} component={RedirectToDashboard} />
|
||||
<Route path={`${CLOUD_SECURITY_POSTURE_BASE_PATH}/*`} component={UnknownRoute} />
|
||||
</Switch>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
|
||||
import { CspApp } from './app';
|
||||
import type { CspClientPluginStartDeps } from '../types';
|
||||
|
||||
export const renderApp = (
|
||||
core: CoreStart,
|
||||
deps: CspClientPluginStartDeps,
|
||||
params: AppMountParameters
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={params.theme$}>
|
||||
<CspApp core={core} params={params} deps={deps} />
|
||||
</KibanaThemeProvider>,
|
||||
params.element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(params.element);
|
||||
};
|
|
@ -6,18 +6,17 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { INTERNAL_FEATURE_FLAGS } from '../../../common/constants';
|
||||
import type { CspPage, CspPageNavigationItem } from './types';
|
||||
|
||||
const NAV_ITEMS_NAMES = {
|
||||
DASHBOARD: i18n.translate('xpack.csp.navigation.dashboardNavItemLabel', {
|
||||
defaultMessage: 'Dashboard',
|
||||
defaultMessage: 'Cloud Posture',
|
||||
}),
|
||||
FINDINGS: i18n.translate('xpack.csp.navigation.findingsNavItemLabel', {
|
||||
defaultMessage: 'Findings',
|
||||
}),
|
||||
BENCHMARKS: i18n.translate('xpack.csp.navigation.myBenchmarksNavItemLabel', {
|
||||
defaultMessage: 'My Benchmarks',
|
||||
defaultMessage: 'CSP Benchmarks',
|
||||
}),
|
||||
RULES: i18n.translate('xpack.csp.navigation.rulesNavItemLabel', {
|
||||
defaultMessage: 'Rules',
|
||||
|
@ -41,14 +40,12 @@ export const cloudPosturePages: Record<CspPage, CspPageNavigationItem> = {
|
|||
rules: {
|
||||
name: NAV_ITEMS_NAMES.RULES,
|
||||
path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/benchmarks/:packagePolicyId/:policyId/rules`,
|
||||
disabled: !INTERNAL_FEATURE_FLAGS.showBenchmarks,
|
||||
id: 'cloud_security_posture-rules',
|
||||
},
|
||||
benchmarks: {
|
||||
name: NAV_ITEMS_NAMES.BENCHMARKS,
|
||||
path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/benchmarks`,
|
||||
exact: true,
|
||||
disabled: !INTERNAL_FEATURE_FLAGS.showBenchmarks,
|
||||
id: 'cloud_security_posture-benchmarks',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,34 +6,34 @@
|
|||
*/
|
||||
|
||||
import { cloudPosturePages } from './constants';
|
||||
import { getSecuritySolutionLinks } from './security_solution_links';
|
||||
import { getSecuritySolutionLink, getSecuritySolutionNavTab } from './security_solution_links';
|
||||
import { Chance } from 'chance';
|
||||
import type { CspPage } from './types';
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
describe('getSecuritySolutionLinks', () => {
|
||||
describe('getSecuritySolutionLink', () => {
|
||||
it('gets the correct link properties', () => {
|
||||
const cspPage = chance.pickone<CspPage>(['dashboard', 'findings', 'benchmarks', 'rules']);
|
||||
|
||||
const links = getSecuritySolutionLinks(cspPage);
|
||||
const link = getSecuritySolutionLink(cspPage);
|
||||
|
||||
expect(links.id).toEqual(cloudPosturePages[cspPage].id);
|
||||
expect(links.path).toEqual(cloudPosturePages[cspPage].path);
|
||||
expect(links.title).toEqual(cloudPosturePages[cspPage].name);
|
||||
});
|
||||
|
||||
it('de-structures extensions correctly', () => {
|
||||
const cspPage = chance.pickone<CspPage>(['dashboard', 'findings', 'benchmarks', 'rules']);
|
||||
const overwrittenTitle = chance.word();
|
||||
const extensions = {
|
||||
[cloudPosturePages[cspPage].id]: { title: overwrittenTitle },
|
||||
};
|
||||
|
||||
const links = getSecuritySolutionLinks(cspPage, extensions);
|
||||
|
||||
expect(links.id).toEqual(cloudPosturePages[cspPage].id);
|
||||
expect(links.path).toEqual(cloudPosturePages[cspPage].path);
|
||||
expect(links.title).toEqual(overwrittenTitle);
|
||||
expect(link.id).toEqual(cloudPosturePages[cspPage].id);
|
||||
expect(link.path).toEqual(cloudPosturePages[cspPage].path);
|
||||
expect(link.title).toEqual(cloudPosturePages[cspPage].name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSecuritySolutionNavTab', () => {
|
||||
it('gets the correct nav tab properties', () => {
|
||||
const cspPage = chance.pickone<CspPage>(['dashboard', 'findings', 'benchmarks', 'rules']);
|
||||
const basePath = chance.word();
|
||||
|
||||
const navTab = getSecuritySolutionNavTab(cspPage, basePath);
|
||||
|
||||
expect(navTab.id).toEqual(cloudPosturePages[cspPage].id);
|
||||
expect(navTab.name).toEqual(cloudPosturePages[cspPage].name);
|
||||
expect(navTab.href).toEqual(`${basePath}${cloudPosturePages[cspPage].path}`);
|
||||
expect(navTab.disabled).toEqual(!!cloudPosturePages[cspPage].disabled);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,52 +8,42 @@
|
|||
import { cloudPosturePages } from './constants';
|
||||
import type { CloudSecurityPosturePageId, CspPage } from './types';
|
||||
|
||||
interface BaseLinkItem {
|
||||
id: string;
|
||||
interface CloudSecurityPostureLinkItem<TId extends string = CloudSecurityPosturePageId> {
|
||||
id: TId;
|
||||
title: string;
|
||||
path: string;
|
||||
links?: BaseLinkItem[];
|
||||
}
|
||||
|
||||
type SecuritySolutionLinkExtensions<TLinkItem extends BaseLinkItem> = Partial<
|
||||
Record<CloudSecurityPosturePageId, Partial<TLinkItem>>
|
||||
>;
|
||||
|
||||
export const getSecuritySolutionLinks = <TLinkItem extends BaseLinkItem = BaseLinkItem>(
|
||||
cspPage: CspPage,
|
||||
extensions?: SecuritySolutionLinkExtensions<TLinkItem>
|
||||
): TLinkItem =>
|
||||
({
|
||||
id: cloudPosturePages[cspPage].id,
|
||||
title: cloudPosturePages[cspPage].name,
|
||||
path: cloudPosturePages[cspPage].path,
|
||||
...(extensions?.[cloudPosturePages[cspPage].id] ?? {}),
|
||||
} as TLinkItem);
|
||||
interface CloudSecurityPostureNavTab<TId extends string = CloudSecurityPosturePageId> {
|
||||
id: TId;
|
||||
name: string;
|
||||
href: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cloud security posture links for top-level navigation in the security solution.
|
||||
* @param extensions extended configuration for the links.
|
||||
* Gets the cloud security posture link properties of a CSP page for navigation in the security solution.
|
||||
* @param cloudSecurityPosturePage the name of the cloud posture page.
|
||||
*/
|
||||
export const getSecuritySolutionRootLinks = <TLinkItem extends BaseLinkItem = BaseLinkItem>(
|
||||
extensions?: SecuritySolutionLinkExtensions<TLinkItem>
|
||||
): TLinkItem => getSecuritySolutionLinks('findings', extensions);
|
||||
export const getSecuritySolutionLink = <TId extends string = CloudSecurityPosturePageId>(
|
||||
cloudSecurityPosturePage: CspPage
|
||||
): CloudSecurityPostureLinkItem<TId> => ({
|
||||
id: cloudPosturePages[cloudSecurityPosturePage].id as TId,
|
||||
title: cloudPosturePages[cloudSecurityPosturePage].name,
|
||||
path: cloudPosturePages[cloudSecurityPosturePage].path,
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the cloud security posture links for navigation in the security solution's "Dashboards" section.
|
||||
* @param extensions extended configuration for the links.
|
||||
* Gets the cloud security posture link properties of a CSP page for navigation in the old security solution navigation.
|
||||
* @param cloudSecurityPosturePage the name of the cloud posture page.
|
||||
* @param basePath the base path for links.
|
||||
*/
|
||||
export const getSecuritySolutionDashboardLinks = <TLinkItem extends BaseLinkItem = BaseLinkItem>(
|
||||
extensions?: SecuritySolutionLinkExtensions<TLinkItem>
|
||||
): TLinkItem => getSecuritySolutionLinks('dashboard', extensions);
|
||||
|
||||
/**
|
||||
* Gets the cloud security posture links for navigation in the security solution's "Manage" section.
|
||||
* @param extensions extended configuration for the links.
|
||||
*/
|
||||
export const getSecuritySolutionManageLinks = <TLinkItem extends BaseLinkItem = BaseLinkItem>(
|
||||
extensions?: SecuritySolutionLinkExtensions<TLinkItem>
|
||||
): TLinkItem => {
|
||||
const manageLinks = getSecuritySolutionLinks('benchmarks', extensions);
|
||||
manageLinks.links = [getSecuritySolutionLinks('rules', extensions)];
|
||||
return manageLinks;
|
||||
};
|
||||
export const getSecuritySolutionNavTab = <TId extends string = CloudSecurityPosturePageId>(
|
||||
cloudSecurityPosturePage: CspPage,
|
||||
basePath: string
|
||||
): CloudSecurityPostureNavTab<TId> => ({
|
||||
id: cloudPosturePages[cloudSecurityPosturePage].id as TId,
|
||||
name: cloudPosturePages[cloudSecurityPosturePage].name,
|
||||
href: `${basePath}${cloudPosturePages[cloudSecurityPosturePage].path}`,
|
||||
disabled: !!cloudPosturePages[cloudSecurityPosturePage].disabled,
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ export type CloudSecurityPosturePageId =
|
|||
| 'cloud_security_posture-benchmarks'
|
||||
| 'cloud_security_posture-rules';
|
||||
|
||||
/** An entry for the cloud security posture breadcrumbs implementation. */
|
||||
export interface BreadcrumbEntry {
|
||||
readonly name: string;
|
||||
readonly path: string;
|
||||
|
|
|
@ -22,7 +22,7 @@ export const useCISIntegrationLink = (): string | undefined => {
|
|||
version: cisIntegration.data.item.version,
|
||||
}),
|
||||
})
|
||||
.join('/');
|
||||
.join('');
|
||||
|
||||
return http.basePath.prepend(path);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import { EuiEmptyPrompt } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { NoDataPage } from '@kbn/kibana-react-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { FullSizeCenteredPage } from './full_size_centered_page';
|
||||
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
|
||||
import { CspLoadingState } from './csp_loading_state';
|
||||
import { useCISIntegrationLink } from '../common/navigation/use_navigate_to_cis_integration';
|
||||
|
@ -42,37 +43,39 @@ export const isCommonError = (error: unknown): error is CommonError => {
|
|||
};
|
||||
|
||||
const packageNotInstalledRenderer = (cisIntegrationLink?: string) => (
|
||||
<NoDataPage
|
||||
data-test-subj={PACKAGE_NOT_INSTALLED_TEST_SUBJECT}
|
||||
css={css`
|
||||
max-width: 950px;
|
||||
margin-top: 50px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
`}
|
||||
pageTitle={i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.pageTitle', {
|
||||
defaultMessage: 'Install Integration to get started',
|
||||
})}
|
||||
solution={i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.solutionNameLabel', {
|
||||
defaultMessage: 'Cloud Security Posture',
|
||||
})}
|
||||
// TODO: Add real docs link once we have it
|
||||
docsLink={'https://www.elastic.co/guide/index.html'}
|
||||
logo={'logoSecurity'}
|
||||
actions={{
|
||||
elasticAgent: {
|
||||
href: cisIntegrationLink,
|
||||
isDisabled: !cisIntegrationLink,
|
||||
title: i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.buttonLabel', {
|
||||
defaultMessage: 'Add a CIS integration',
|
||||
}),
|
||||
description: i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.description', {
|
||||
defaultMessage:
|
||||
'Use our CIS Kubernetes Benchmark integration to measure your Kubernetes cluster setup against the CIS recommendations.',
|
||||
}),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FullSizeCenteredPage>
|
||||
<NoDataPage
|
||||
data-test-subj={PACKAGE_NOT_INSTALLED_TEST_SUBJECT}
|
||||
css={css`
|
||||
max-width: 950px;
|
||||
`}
|
||||
pageTitle={i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.pageTitle', {
|
||||
defaultMessage: 'Install Integration to get started',
|
||||
})}
|
||||
solution={i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.solutionNameLabel', {
|
||||
defaultMessage: 'Cloud Security Posture',
|
||||
})}
|
||||
// TODO: Add real docs link once we have it
|
||||
docsLink={'https://www.elastic.co/guide/index.html'}
|
||||
logo={'logoSecurity'}
|
||||
actions={{
|
||||
elasticAgent: {
|
||||
href: cisIntegrationLink,
|
||||
isDisabled: !cisIntegrationLink,
|
||||
title: i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.buttonLabel', {
|
||||
defaultMessage: 'Add a CIS integration',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.csp.cloudPosturePage.packageNotInstalled.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Use our CIS Kubernetes Benchmark integration to measure your Kubernetes cluster setup against the CIS recommendations.',
|
||||
}
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FullSizeCenteredPage>
|
||||
);
|
||||
|
||||
const defaultLoadingRenderer = () => (
|
||||
|
@ -85,57 +88,58 @@ const defaultLoadingRenderer = () => (
|
|||
);
|
||||
|
||||
const defaultErrorRenderer = (error: unknown) => (
|
||||
<EuiEmptyPrompt
|
||||
css={css`
|
||||
margin-top: 50px;
|
||||
`}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj={ERROR_STATE_TEST_SUBJECT}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.errorRenderer.errorTitle"
|
||||
defaultMessage="We couldn't fetch your cloud security posture data"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
isCommonError(error) ? (
|
||||
<p>
|
||||
<FullSizeCenteredPage>
|
||||
<EuiEmptyPrompt
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj={ERROR_STATE_TEST_SUBJECT}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.errorRenderer.errorDescription"
|
||||
defaultMessage="{error} {statusCode}: {body}"
|
||||
values={{
|
||||
error: error.body.error,
|
||||
statusCode: error.body.statusCode,
|
||||
body: error.body.message,
|
||||
}}
|
||||
id="xpack.csp.cloudPosturePage.errorRenderer.errorTitle"
|
||||
defaultMessage="We couldn't fetch your cloud security posture data"
|
||||
/>
|
||||
</p>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
isCommonError(error) ? (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.errorRenderer.errorDescription"
|
||||
defaultMessage="{error} {statusCode}: {body}"
|
||||
values={{
|
||||
error: error.body.error,
|
||||
statusCode: error.body.statusCode,
|
||||
body: error.body.message,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</FullSizeCenteredPage>
|
||||
);
|
||||
|
||||
const defaultNoDataRenderer = () => {
|
||||
return (
|
||||
<NoDataPage
|
||||
data-test-subj={DEFAULT_NO_DATA_TEST_SUBJECT}
|
||||
css={css`
|
||||
margin-top: 50px;
|
||||
`}
|
||||
pageTitle={i18n.translate('xpack.csp.cloudPosturePage.defaultNoDataConfig.pageTitle', {
|
||||
defaultMessage: 'No data found',
|
||||
})}
|
||||
solution={i18n.translate('xpack.csp.cloudPosturePage.defaultNoDataConfig.solutionNameLabel', {
|
||||
defaultMessage: 'Cloud Security Posture',
|
||||
})}
|
||||
// TODO: Add real docs link once we have it
|
||||
docsLink={'https://www.elastic.co/guide/index.html'}
|
||||
logo={'logoSecurity'}
|
||||
actions={{}}
|
||||
/>
|
||||
<FullSizeCenteredPage>
|
||||
<NoDataPage
|
||||
data-test-subj={DEFAULT_NO_DATA_TEST_SUBJECT}
|
||||
pageTitle={i18n.translate('xpack.csp.cloudPosturePage.defaultNoDataConfig.pageTitle', {
|
||||
defaultMessage: 'No data found',
|
||||
})}
|
||||
solution={i18n.translate(
|
||||
'xpack.csp.cloudPosturePage.defaultNoDataConfig.solutionNameLabel',
|
||||
{
|
||||
defaultMessage: 'Cloud Security Posture',
|
||||
}
|
||||
)}
|
||||
// TODO: Add real docs link once we have it
|
||||
docsLink={'https://www.elastic.co/guide/index.html'}
|
||||
logo={'logoSecurity'}
|
||||
actions={{}}
|
||||
/>
|
||||
</FullSizeCenteredPage>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { Score } from '../../common/types';
|
||||
|
||||
interface Props {
|
||||
value: Score;
|
||||
}
|
||||
|
||||
export const CspHealthBadge = ({ value }: Props) => {
|
||||
if (value <= 65) {
|
||||
return (
|
||||
<EuiBadge color="danger">
|
||||
<FormattedMessage id="xpack.csp.cspHealthBadge.criticalLabel" defaultMessage="Critical" />
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
if (value <= 86) {
|
||||
return (
|
||||
<EuiBadge color="warning">
|
||||
<FormattedMessage id="xpack.csp.cspHealthBadge.warningLabel" defaultMessage="Warning" />
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
if (value <= 100) {
|
||||
return (
|
||||
<EuiBadge color="success">
|
||||
<FormattedMessage id="xpack.csp.cspHealthBadge.healthyLabel" defaultMessage="Healthy" />
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -5,29 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, useEuiTheme } from '@elastic/eui';
|
||||
import { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { FullSizeCenteredPage } from './full_size_centered_page';
|
||||
|
||||
// Keep this component lean as it is part of the main app bundle
|
||||
export const CspLoadingState: React.FunctionComponent<{ ['data-test-subj']?: string }> = ({
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
padding: ${euiTheme.size.l};
|
||||
margin-top: 50px;
|
||||
`}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
data-test-subj={rest['data-test-subj']}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{children}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<FullSizeCenteredPage data-test-subj={rest['data-test-subj']}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
<EuiSpacer />
|
||||
{children}
|
||||
</FullSizeCenteredPage>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import Chance from 'chance';
|
||||
import { createPageNavigationItemFixture } from '../test/fixtures/navigation_item';
|
||||
import { getSideNavItems } from './csp_page_template';
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
describe('getSideNavItems', () => {
|
||||
it('maps navigation items to side navigation items', () => {
|
||||
const navigationItem = createPageNavigationItemFixture();
|
||||
const id = chance.word();
|
||||
const sideNavItems = getSideNavItems({ [id]: navigationItem });
|
||||
|
||||
expect(sideNavItems).toHaveLength(1);
|
||||
expect(sideNavItems[0]).toMatchObject({
|
||||
id,
|
||||
name: navigationItem.name,
|
||||
renderItem: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not map disabled navigation items to side navigation items', () => {
|
||||
const navigationItem = createPageNavigationItemFixture({ disabled: true });
|
||||
const id = chance.word();
|
||||
const sideNavItems = getSideNavItems({ [id]: navigationItem });
|
||||
expect(sideNavItems).toHaveLength(0);
|
||||
});
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { KibanaPageTemplate, type KibanaPageTemplateProps } from '@kbn/shared-ux-components';
|
||||
import { cloudPosturePages } from '../common/navigation/constants';
|
||||
import type { CspNavigationItem } from '../common/navigation/types';
|
||||
|
||||
const activeItemStyle = { fontWeight: 700 };
|
||||
|
||||
export const getSideNavItems = (
|
||||
navigationItems: Record<string, CspNavigationItem>
|
||||
): NonNullable<NonNullable<KibanaPageTemplateProps['solutionNav']>['items']> =>
|
||||
Object.entries(navigationItems)
|
||||
.filter(([_, navigationItem]) => !navigationItem.disabled)
|
||||
.map(([id, navigationItem]) => ({
|
||||
id,
|
||||
name: navigationItem.name,
|
||||
renderItem: () => (
|
||||
<NavLink to={navigationItem.path} activeStyle={activeItemStyle}>
|
||||
{navigationItem.name}
|
||||
</NavLink>
|
||||
),
|
||||
}));
|
||||
|
||||
const DEFAULT_PAGE_PROPS: KibanaPageTemplateProps = {
|
||||
solutionNav: {
|
||||
name: i18n.translate('xpack.csp.cspPageTemplate.navigationTitle', {
|
||||
defaultMessage: 'Cloud Security Posture',
|
||||
}),
|
||||
items: getSideNavItems({
|
||||
dashboard: cloudPosturePages.dashboard,
|
||||
findings: cloudPosturePages.findings,
|
||||
benchmark: cloudPosturePages.benchmarks,
|
||||
}),
|
||||
},
|
||||
restrictWidth: false,
|
||||
};
|
||||
|
||||
export const CspPageTemplate = <TData, TError>({
|
||||
children,
|
||||
...kibanaPageTemplateProps
|
||||
}: KibanaPageTemplateProps) => {
|
||||
return (
|
||||
<KibanaPageTemplate {...DEFAULT_PAGE_PROPS} {...kibanaPageTemplateProps}>
|
||||
<EuiErrorBoundary>{children}</EuiErrorBoundary>
|
||||
</KibanaPageTemplate>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { EuiFlexGroup, type CommonProps } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import React from 'react';
|
||||
|
||||
// Keep this component lean as it is part of the main app bundle
|
||||
export const FullSizeCenteredPage = ({
|
||||
children,
|
||||
...rest
|
||||
}: { children: React.ReactNode } & CommonProps) => (
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
// 140px is roughly the Kibana chrome with a bit of space to spare
|
||||
min-height: calc(100vh - 140px);
|
||||
`}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
direction="column"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</EuiFlexGroup>
|
||||
);
|
|
@ -8,6 +8,7 @@
|
|||
import React from 'react';
|
||||
import { EuiLoadingLogo, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { FullSizeCenteredPage } from './full_size_centered_page';
|
||||
import { useCspBenchmarkIntegrations } from '../pages/benchmarks/use_csp_benchmark_integrations';
|
||||
import { useCISIntegrationPoliciesLink } from '../common/navigation/use_navigate_to_cis_integration_policies';
|
||||
import { NO_FINDINGS_STATUS_TEST_SUBJ } from './test_subjects';
|
||||
|
@ -131,5 +132,9 @@ export const NoFindingsStates = () => {
|
|||
if (status === 'index-timeout') return <IndexTimeout />; // agent added, index timeout has passed
|
||||
};
|
||||
|
||||
return <CloudPosturePage query={getSetupStatus}>{render()}</CloudPosturePage>;
|
||||
return (
|
||||
<CloudPosturePage query={getSetupStatus}>
|
||||
<FullSizeCenteredPage>{render()}</FullSizeCenteredPage>
|
||||
</CloudPosturePage>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { CspPageTemplate } from './csp_page_template';
|
||||
|
||||
// TODO: Remove when CSP is rendered exclusively under the security solution
|
||||
export const UnknownRoute = React.memo(() => (
|
||||
<CspPageTemplate template="centeredContent">
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj="unknownRoute"
|
||||
iconColor="default"
|
||||
iconType="logoElastic"
|
||||
title={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.unknownRoute.pageNotFoundTitle"
|
||||
defaultMessage="Page not found"
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</CspPageTemplate>
|
||||
));
|
|
@ -10,9 +10,8 @@ export type { CspSecuritySolutionContext } from './types';
|
|||
export { CLOUD_SECURITY_POSTURE_BASE_PATH } from './common/navigation/constants';
|
||||
export type { CloudSecurityPosturePageId } from './common/navigation/types';
|
||||
export {
|
||||
getSecuritySolutionRootLinks,
|
||||
getSecuritySolutionDashboardLinks,
|
||||
getSecuritySolutionManageLinks,
|
||||
getSecuritySolutionLink,
|
||||
getSecuritySolutionNavTab,
|
||||
} from './common/navigation/security_solution_links';
|
||||
|
||||
export type { CspClientPluginSetup, CspClientPluginStart } from './types';
|
||||
|
|
|
@ -21,10 +21,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CloudPosturePage } from '../../components/cloud_posture_page';
|
||||
import { cloudPosturePages } from '../../common/navigation/constants';
|
||||
import { useCspBreadcrumbs } from '../../common/navigation/use_csp_breadcrumbs';
|
||||
import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration';
|
||||
import { CspPageTemplate } from '../../components/csp_page_template';
|
||||
import { BenchmarksTable } from './benchmarks_table';
|
||||
import {
|
||||
useCspBenchmarkIntegrations,
|
||||
|
@ -33,7 +30,6 @@ import {
|
|||
import { extractErrorMessage } from '../../../common/utils/helpers';
|
||||
import * as TEST_SUBJ from './test_subjects';
|
||||
|
||||
const BENCHMARKS_BREADCRUMBS = [cloudPosturePages.benchmarks];
|
||||
const SEARCH_DEBOUNCE_MS = 300;
|
||||
|
||||
const AddCisIntegrationButton = () => {
|
||||
|
@ -128,7 +124,7 @@ const BenchmarkSearchField = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const BenchmarksNoPageTemplate = () => {
|
||||
export const Benchmarks = () => {
|
||||
const [query, setQuery] = useState<UseCspBenchmarkIntegrationsProps>({
|
||||
name: '',
|
||||
page: 1,
|
||||
|
@ -194,13 +190,3 @@ export const BenchmarksNoPageTemplate = () => {
|
|||
</CloudPosturePage>
|
||||
);
|
||||
};
|
||||
|
||||
export const Benchmarks = () => {
|
||||
useCspBreadcrumbs(BENCHMARKS_BREADCRUMBS);
|
||||
|
||||
return (
|
||||
<CspPageTemplate>
|
||||
<BenchmarksNoPageTemplate />
|
||||
</CspPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { Benchmarks, BenchmarksNoPageTemplate } from './benchmarks';
|
||||
export { Benchmarks } from './benchmarks';
|
||||
|
|
|
@ -9,18 +9,15 @@ import React from 'react';
|
|||
import { EuiSpacer, EuiPageHeader } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import { useCspBreadcrumbs } from '../../common/navigation/use_csp_breadcrumbs';
|
||||
import { cloudPosturePages } from '../../common/navigation/constants';
|
||||
import { CloudPosturePage } from '../../components/cloud_posture_page';
|
||||
import { DASHBOARD_CONTAINER } from './test_subjects';
|
||||
import { SummarySection } from './dashboard_sections/summary_section';
|
||||
import { BenchmarksSection } from './dashboard_sections/benchmarks_section';
|
||||
import { useComplianceDashboardDataApi } from '../../common/api';
|
||||
import { CspPageTemplate } from '../../components/csp_page_template';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
import { NoFindingsStates } from '../../components/no_findings_states';
|
||||
|
||||
export const ComplianceDashboardNoPageTemplate = () => {
|
||||
export const ComplianceDashboard = () => {
|
||||
const getSetupStatus = useCspSetupStatusApi();
|
||||
const hasFindings = getSetupStatus.data?.status === 'indexed';
|
||||
const getDashboardData = useComplianceDashboardDataApi({
|
||||
|
@ -54,15 +51,3 @@ export const ComplianceDashboardNoPageTemplate = () => {
|
|||
</CloudPosturePage>
|
||||
);
|
||||
};
|
||||
|
||||
const COMPLIANCE_DASHBOARD_BREADCRUMBS = [cloudPosturePages.dashboard];
|
||||
|
||||
export const ComplianceDashboard = () => {
|
||||
useCspBreadcrumbs(COMPLIANCE_DASHBOARD_BREADCRUMBS);
|
||||
|
||||
return (
|
||||
<CspPageTemplate>
|
||||
<ComplianceDashboardNoPageTemplate />
|
||||
</CspPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,17 +9,15 @@ import type { UseQueryResult } from 'react-query';
|
|||
import { Redirect, Switch, Route, useLocation } from 'react-router-dom';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
import { NoFindingsStates } from '../../components/no_findings_states';
|
||||
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';
|
||||
import { useLatestFindingsDataView } from '../../common/api/use_latest_findings_data_view';
|
||||
import { cloudPosturePages, findingsNavigation } from '../../common/navigation/constants';
|
||||
import { CspPageTemplate } from '../../components/csp_page_template';
|
||||
import { FindingsByResourceContainer } from './latest_findings_by_resource/findings_by_resource_container';
|
||||
import { LatestFindingsContainer } from './latest_findings/latest_findings_container';
|
||||
|
||||
export const FindingsNoPageTemplate = () => {
|
||||
export const Findings = () => {
|
||||
const location = useLocation();
|
||||
const dataViewQuery = useLatestFindingsDataView();
|
||||
// TODO: Consider splitting the PIT window so that each "group by" view has its own PIT
|
||||
|
@ -74,15 +72,3 @@ export const FindingsNoPageTemplate = () => {
|
|||
</CloudPosturePage>
|
||||
);
|
||||
};
|
||||
|
||||
const FINDINGS_BREADCRUMBS = [cloudPosturePages.findings];
|
||||
|
||||
export const Findings = () => {
|
||||
useCspBreadcrumbs(FINDINGS_BREADCRUMBS);
|
||||
|
||||
return (
|
||||
<CspPageTemplate paddingSize="none">
|
||||
<FindingsNoPageTemplate />
|
||||
</CspPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
useBaseEsQuery,
|
||||
usePersistedQuery,
|
||||
} from '../utils';
|
||||
import { PageWrapper, PageTitle, PageTitleText } from '../layout/findings_layout';
|
||||
import { PageTitle, PageTitleText } from '../layout/findings_layout';
|
||||
import { FindingsGroupBySelector } from '../layout/findings_group_by_selector';
|
||||
import { useUrlQuery } from '../../../common/hooks/use_url_query';
|
||||
import { ErrorCallout } from '../layout/error_callout';
|
||||
|
@ -74,64 +74,62 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => {
|
|||
}}
|
||||
loading={findingsGroupByNone.isFetching}
|
||||
/>
|
||||
<PageWrapper>
|
||||
<LatestFindingsPageTitle />
|
||||
{error && <ErrorCallout error={error} />}
|
||||
{!error && (
|
||||
<>
|
||||
<FindingsGroupBySelector type="default" />
|
||||
{findingsGroupByNone.isSuccess && !!findingsGroupByNone.data.page.length && (
|
||||
<FindingsDistributionBar
|
||||
{...{
|
||||
type: i18n.translate('xpack.csp.findings.latestFindings.tableRowTypeLabel', {
|
||||
defaultMessage: 'Findings',
|
||||
}),
|
||||
total: findingsGroupByNone.data.total,
|
||||
passed: findingsGroupByNone.data.count.passed,
|
||||
failed: findingsGroupByNone.data.count.failed,
|
||||
...getFindingsPageSizeInfo({
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
pageSize: urlQuery.pageSize,
|
||||
currentPageSize: findingsGroupByNone.data.page.length,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<FindingsTable
|
||||
loading={findingsGroupByNone.isFetching}
|
||||
items={findingsGroupByNone.data?.page || []}
|
||||
pagination={getPaginationTableParams({
|
||||
pageSize: urlQuery.pageSize,
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
totalItemCount: findingsGroupByNone.data?.total || 0,
|
||||
})}
|
||||
sorting={{
|
||||
sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction },
|
||||
<LatestFindingsPageTitle />
|
||||
{error && <ErrorCallout error={error} />}
|
||||
{!error && (
|
||||
<>
|
||||
<FindingsGroupBySelector type="default" />
|
||||
{findingsGroupByNone.isSuccess && !!findingsGroupByNone.data.page.length && (
|
||||
<FindingsDistributionBar
|
||||
{...{
|
||||
type: i18n.translate('xpack.csp.findings.latestFindings.tableRowTypeLabel', {
|
||||
defaultMessage: 'Findings',
|
||||
}),
|
||||
total: findingsGroupByNone.data.total,
|
||||
passed: findingsGroupByNone.data.count.passed,
|
||||
failed: findingsGroupByNone.data.count.failed,
|
||||
...getFindingsPageSizeInfo({
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
pageSize: urlQuery.pageSize,
|
||||
currentPageSize: findingsGroupByNone.data.page.length,
|
||||
}),
|
||||
}}
|
||||
setTableOptions={({ page, sort }) =>
|
||||
setUrlQuery({
|
||||
sort,
|
||||
pageIndex: page.index,
|
||||
pageSize: page.size,
|
||||
})
|
||||
}
|
||||
onAddFilter={(field, value, negate) =>
|
||||
setUrlQuery({
|
||||
pageIndex: 0,
|
||||
filters: getFilters({
|
||||
filters: urlQuery.filters,
|
||||
dataView,
|
||||
field,
|
||||
value,
|
||||
negate,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</PageWrapper>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<FindingsTable
|
||||
loading={findingsGroupByNone.isFetching}
|
||||
items={findingsGroupByNone.data?.page || []}
|
||||
pagination={getPaginationTableParams({
|
||||
pageSize: urlQuery.pageSize,
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
totalItemCount: findingsGroupByNone.data?.total || 0,
|
||||
})}
|
||||
sorting={{
|
||||
sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction },
|
||||
}}
|
||||
setTableOptions={({ page, sort }) =>
|
||||
setUrlQuery({
|
||||
sort,
|
||||
pageIndex: page.index,
|
||||
pageSize: page.size,
|
||||
})
|
||||
}
|
||||
onAddFilter={(field, value, negate) =>
|
||||
setUrlQuery({
|
||||
pageIndex: 0,
|
||||
filters: getFilters({
|
||||
filters: urlQuery.filters,
|
||||
dataView,
|
||||
field,
|
||||
value,
|
||||
negate,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
useBaseEsQuery,
|
||||
usePersistedQuery,
|
||||
} from '../utils';
|
||||
import { PageTitle, PageTitleText, PageWrapper } from '../layout/findings_layout';
|
||||
import { PageTitle, PageTitleText } from '../layout/findings_layout';
|
||||
import { FindingsGroupBySelector } from '../layout/findings_group_by_selector';
|
||||
import { findingsNavigation } from '../../../common/navigation/constants';
|
||||
import { ResourceFindings } from './resource_findings/resource_findings_container';
|
||||
|
@ -89,73 +89,71 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => {
|
|||
}}
|
||||
loading={findingsGroupByResource.isFetching}
|
||||
/>
|
||||
<PageWrapper>
|
||||
<PageTitle>
|
||||
<PageTitleText
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.csp.findings.findingsByResource.findingsByResourcePageTitle"
|
||||
defaultMessage="Findings"
|
||||
/>
|
||||
<PageTitle>
|
||||
<PageTitleText
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.csp.findings.findingsByResource.findingsByResourcePageTitle"
|
||||
defaultMessage="Findings"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageTitle>
|
||||
{error && <ErrorCallout error={error} />}
|
||||
{!error && (
|
||||
<>
|
||||
<FindingsGroupBySelector type="resource" />
|
||||
{findingsGroupByResource.isSuccess && !!findingsGroupByResource.data.page.length && (
|
||||
<FindingsDistributionBar
|
||||
{...{
|
||||
type: i18n.translate('xpack.csp.findings.findingsByResource.tableRowTypeLabel', {
|
||||
defaultMessage: 'Resources',
|
||||
}),
|
||||
total: findingsGroupByResource.data.total,
|
||||
passed: findingsGroupByResource.data.count.passed,
|
||||
failed: findingsGroupByResource.data.count.failed,
|
||||
...getFindingsPageSizeInfo({
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
pageSize: urlQuery.pageSize,
|
||||
currentPageSize: findingsGroupByResource.data.page.length,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<FindingsByResourceTable
|
||||
loading={findingsGroupByResource.isFetching}
|
||||
items={findingsGroupByResource.data?.page || []}
|
||||
pagination={getPaginationTableParams({
|
||||
pageSize: urlQuery.pageSize,
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
totalItemCount: findingsGroupByResource.data?.total || 0,
|
||||
})}
|
||||
setTableOptions={({ sort, page }) =>
|
||||
setUrlQuery({
|
||||
sortDirection: sort?.direction,
|
||||
pageIndex: page.index,
|
||||
pageSize: page.size,
|
||||
})
|
||||
}
|
||||
sorting={{
|
||||
sort: { field: 'failed_findings', direction: urlQuery.sortDirection },
|
||||
}}
|
||||
onAddFilter={(field, value, negate) =>
|
||||
setUrlQuery({
|
||||
pageIndex: 0,
|
||||
filters: getFilters({
|
||||
filters: urlQuery.filters,
|
||||
dataView,
|
||||
field,
|
||||
value,
|
||||
negate,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</PageTitle>
|
||||
{error && <ErrorCallout error={error} />}
|
||||
{!error && (
|
||||
<>
|
||||
<FindingsGroupBySelector type="resource" />
|
||||
{findingsGroupByResource.isSuccess && !!findingsGroupByResource.data.page.length && (
|
||||
<FindingsDistributionBar
|
||||
{...{
|
||||
type: i18n.translate('xpack.csp.findings.findingsByResource.tableRowTypeLabel', {
|
||||
defaultMessage: 'Resources',
|
||||
}),
|
||||
total: findingsGroupByResource.data.total,
|
||||
passed: findingsGroupByResource.data.count.passed,
|
||||
failed: findingsGroupByResource.data.count.failed,
|
||||
...getFindingsPageSizeInfo({
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
pageSize: urlQuery.pageSize,
|
||||
currentPageSize: findingsGroupByResource.data.page.length,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<FindingsByResourceTable
|
||||
loading={findingsGroupByResource.isFetching}
|
||||
items={findingsGroupByResource.data?.page || []}
|
||||
pagination={getPaginationTableParams({
|
||||
pageSize: urlQuery.pageSize,
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
totalItemCount: findingsGroupByResource.data?.total || 0,
|
||||
})}
|
||||
setTableOptions={({ sort, page }) =>
|
||||
setUrlQuery({
|
||||
sortDirection: sort?.direction,
|
||||
pageIndex: page.index,
|
||||
pageSize: page.size,
|
||||
})
|
||||
}
|
||||
sorting={{
|
||||
sort: { field: 'failed_findings', direction: urlQuery.sortDirection },
|
||||
}}
|
||||
onAddFilter={(field, value, negate) =>
|
||||
setUrlQuery({
|
||||
pageIndex: 0,
|
||||
filters: getFilters({
|
||||
filters: urlQuery.filters,
|
||||
dataView,
|
||||
field,
|
||||
value,
|
||||
negate,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</PageWrapper>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useEuiTheme } from '@elastic/eui';
|
|||
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 { PageTitle, PageTitleText } from '../../layout/findings_layout';
|
||||
import { findingsNavigation } from '../../../../common/navigation/constants';
|
||||
import { ResourceFindingsQuery, useResourceFindings } from './use_resource_findings';
|
||||
import { useUrlQuery } from '../../../../common/hooks/use_url_query';
|
||||
|
@ -93,73 +93,71 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => {
|
|||
}}
|
||||
loading={resourceFindings.isFetching}
|
||||
/>
|
||||
<PageWrapper>
|
||||
<PageTitle>
|
||||
<BackToResourcesButton />
|
||||
<PageTitleText
|
||||
title={
|
||||
<div style={{ padding: euiTheme.size.s }}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.findings.resourceFindings.resourceFindingsPageTitle"
|
||||
defaultMessage="{resourceId} - Findings"
|
||||
values={{ resourceId: params.resourceId }}
|
||||
/>
|
||||
</div>
|
||||
<PageTitle>
|
||||
<BackToResourcesButton />
|
||||
<PageTitleText
|
||||
title={
|
||||
<div style={{ padding: euiTheme.size.s }}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.findings.resourceFindings.resourceFindingsPageTitle"
|
||||
defaultMessage="{resourceId} - Findings"
|
||||
values={{ resourceId: params.resourceId }}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</PageTitle>
|
||||
<EuiSpacer />
|
||||
{error && <ErrorCallout error={error} />}
|
||||
{!error && (
|
||||
<>
|
||||
{resourceFindings.isSuccess && !!resourceFindings.data.page.length && (
|
||||
<FindingsDistributionBar
|
||||
{...{
|
||||
type: i18n.translate('xpack.csp.findings.resourceFindings.tableRowTypeLabel', {
|
||||
defaultMessage: 'Findings',
|
||||
}),
|
||||
total: resourceFindings.data.total,
|
||||
passed: resourceFindings.data.count.passed,
|
||||
failed: resourceFindings.data.count.failed,
|
||||
...getFindingsPageSizeInfo({
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
pageSize: urlQuery.pageSize,
|
||||
currentPageSize: resourceFindings.data.page.length,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<ResourceFindingsTable
|
||||
loading={resourceFindings.isFetching}
|
||||
items={resourceFindings.data?.page || []}
|
||||
pagination={getPaginationTableParams({
|
||||
pageSize: urlQuery.pageSize,
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
totalItemCount: resourceFindings.data?.total || 0,
|
||||
})}
|
||||
sorting={{
|
||||
sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction },
|
||||
}}
|
||||
setTableOptions={({ page, sort }) =>
|
||||
setUrlQuery({ pageIndex: page.index, pageSize: page.size, sort })
|
||||
}
|
||||
onAddFilter={(field, value, negate) =>
|
||||
setUrlQuery({
|
||||
pageIndex: 0,
|
||||
filters: getFilters({
|
||||
filters: urlQuery.filters,
|
||||
dataView,
|
||||
field,
|
||||
value,
|
||||
negate,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</PageTitle>
|
||||
<EuiSpacer />
|
||||
{error && <ErrorCallout error={error} />}
|
||||
{!error && (
|
||||
<>
|
||||
{resourceFindings.isSuccess && !!resourceFindings.data.page.length && (
|
||||
<FindingsDistributionBar
|
||||
{...{
|
||||
type: i18n.translate('xpack.csp.findings.resourceFindings.tableRowTypeLabel', {
|
||||
defaultMessage: 'Findings',
|
||||
}),
|
||||
total: resourceFindings.data.total,
|
||||
passed: resourceFindings.data.count.passed,
|
||||
failed: resourceFindings.data.count.failed,
|
||||
...getFindingsPageSizeInfo({
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
pageSize: urlQuery.pageSize,
|
||||
currentPageSize: resourceFindings.data.page.length,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<ResourceFindingsTable
|
||||
loading={resourceFindings.isFetching}
|
||||
items={resourceFindings.data?.page || []}
|
||||
pagination={getPaginationTableParams({
|
||||
pageSize: urlQuery.pageSize,
|
||||
pageIndex: urlQuery.pageIndex,
|
||||
totalItemCount: resourceFindings.data?.total || 0,
|
||||
})}
|
||||
sorting={{
|
||||
sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction },
|
||||
}}
|
||||
setTableOptions={({ page, sort }) =>
|
||||
setUrlQuery({ pageIndex: page.index, pageSize: page.size, sort })
|
||||
}
|
||||
onAddFilter={(field, value, negate) =>
|
||||
setUrlQuery({
|
||||
pageIndex: 0,
|
||||
filters: getFilters({
|
||||
filters: urlQuery.filters,
|
||||
dataView,
|
||||
field,
|
||||
value,
|
||||
negate,
|
||||
}),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</PageWrapper>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
EuiTitle,
|
||||
EuiToolTip,
|
||||
PropsOf,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import moment from 'moment';
|
||||
|
@ -27,20 +26,6 @@ import {
|
|||
FINDINGS_TABLE_CELL_ADD_NEGATED_FILTER,
|
||||
} from '../test_subjects';
|
||||
|
||||
// TODO: Remove when CSP is rendered exclusively under the security solution
|
||||
export const PageWrapper: React.FC = ({ children }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
padding: ${euiTheme.size.l};
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageTitle: React.FC = ({ children }) => (
|
||||
<EuiTitle size="l">
|
||||
<div>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { Findings, FindingsNoPageTemplate } from './findings';
|
||||
export { Findings } from './findings';
|
||||
export * from './compliance_dashboard';
|
||||
export { Benchmarks, BenchmarksNoPageTemplate } from './benchmarks';
|
||||
export { Rules, RulesNoPageTemplate } from './rules';
|
||||
export { Benchmarks } from './benchmarks';
|
||||
export { Rules } from './rules';
|
||||
|
|
|
@ -15,7 +15,6 @@ import { RulesContainer, type PageUrlParams } from './rules_container';
|
|||
import { cloudPosturePages } from '../../common/navigation/constants';
|
||||
import { useCspBreadcrumbs } from '../../common/navigation/use_csp_breadcrumbs';
|
||||
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';
|
||||
|
@ -40,7 +39,7 @@ const getRulesBreadcrumbs = (
|
|||
return breadCrumbs;
|
||||
};
|
||||
|
||||
export const RulesNoPageTemplate = ({ match: { params } }: RouteComponentProps<PageUrlParams>) => {
|
||||
export const Rules = ({ match: { params } }: RouteComponentProps<PageUrlParams>) => {
|
||||
const { http } = useKibana().services;
|
||||
const integrationInfo = useCspIntegrationInfo(params);
|
||||
const securitySolutionContext = useContext(SecuritySolutionContext);
|
||||
|
@ -112,11 +111,3 @@ export const RulesNoPageTemplate = ({ match: { params } }: RouteComponentProps<P
|
|||
</CloudPosturePage>
|
||||
);
|
||||
};
|
||||
|
||||
export const Rules = (props: RouteComponentProps<PageUrlParams>) => {
|
||||
return (
|
||||
<CspPageTemplate>
|
||||
<RulesNoPageTemplate {...props} />
|
||||
</CspPageTemplate>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,35 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import type { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
|
||||
import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
||||
import { CspLoadingState } from './components/csp_loading_state';
|
||||
import type { CspRouterProps } from './application/csp_router';
|
||||
import { CLOUD_SECURITY_POSTURE_BASE_PATH } from './common/navigation/constants';
|
||||
import type {
|
||||
CspClientPluginSetup,
|
||||
CspClientPluginStart,
|
||||
CspClientPluginSetupDeps,
|
||||
CspClientPluginStartDeps,
|
||||
} from './types';
|
||||
import { PLUGIN_NAME, PLUGIN_ID } from '../common';
|
||||
|
||||
const CspRouterLazy = lazy(() => import('./application/csp_router'));
|
||||
const CspRouter = (props: CspRouterProps) => (
|
||||
<Suspense
|
||||
fallback={
|
||||
<EuiLoadingSpinner
|
||||
size="xl"
|
||||
css={css`
|
||||
margin-top: 50px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Suspense fallback={<CspLoadingState />}>
|
||||
<CspRouterLazy {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
|
@ -51,23 +37,6 @@ export class CspPlugin
|
|||
core: CoreSetup<CspClientPluginStartDeps, CspClientPluginStart>,
|
||||
plugins: CspClientPluginSetupDeps
|
||||
): CspClientPluginSetup {
|
||||
// Register an application into the side navigation menu
|
||||
core.application.register({
|
||||
id: PLUGIN_ID,
|
||||
title: PLUGIN_NAME,
|
||||
euiIconType: 'logoSecurity',
|
||||
category: DEFAULT_APP_CATEGORIES.security,
|
||||
defaultPath: CLOUD_SECURITY_POSTURE_BASE_PATH,
|
||||
async mount(params: AppMountParameters) {
|
||||
// Load application bundle
|
||||
const { renderApp } = await import('./application');
|
||||
// Get start services as specified in kibana.json
|
||||
const [coreStart, depsStart] = await core.getStartServices();
|
||||
// Render the application
|
||||
return renderApp(coreStart, depsStart, params);
|
||||
},
|
||||
});
|
||||
|
||||
// Return methods that should be available to other plugins
|
||||
return {};
|
||||
}
|
||||
|
@ -77,7 +46,9 @@ export class CspPlugin
|
|||
getCloudSecurityPostureRouter: () => (props: CspRouterProps) =>
|
||||
(
|
||||
<KibanaContextProvider services={{ ...core, ...plugins }}>
|
||||
<CspRouter {...props} />
|
||||
<RedirectAppLinks coreStart={core}>
|
||||
<CspRouter {...props} />
|
||||
</RedirectAppLinks>
|
||||
</KibanaContextProvider>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
|
||||
import React, { useMemo } from 'react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { Router, Switch, Route } from 'react-router-dom';
|
||||
|
@ -15,7 +16,13 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
|||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
||||
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
||||
import type { CspAppDeps } from '../application/app';
|
||||
import type { CspClientPluginStartDeps } from '../types';
|
||||
|
||||
interface CspAppDeps {
|
||||
core: CoreStart;
|
||||
deps: CspClientPluginStartDeps;
|
||||
params: AppMountParameters;
|
||||
}
|
||||
|
||||
export const TestProvider: React.FC<Partial<CspAppDeps>> = ({
|
||||
core = coreMock.createStart(),
|
||||
|
|
|
@ -10,6 +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 { CspRouterProps } from './application/csp_router';
|
||||
import type { BreadcrumbEntry, CloudSecurityPosturePageId } from './common/navigation/types';
|
||||
|
||||
/**
|
||||
|
@ -23,9 +24,7 @@ export interface CspClientPluginSetup {}
|
|||
*/
|
||||
export interface CspClientPluginStart {
|
||||
/** Gets the cloud security posture router component for embedding in the security solution. */
|
||||
getCloudSecurityPostureRouter(): ComponentType<{
|
||||
securitySolutionContext: CspSecuritySolutionContext;
|
||||
}>;
|
||||
getCloudSecurityPostureRouter(): ComponentType<CspRouterProps>;
|
||||
}
|
||||
|
||||
export interface CspClientPluginSetupDeps {
|
||||
|
@ -53,5 +52,5 @@ export interface CspSecuritySolutionContext {
|
|||
/** Gets the `SpyRoute` component for navigation highlighting and breadcrumbs. */
|
||||
getSpyRouteComponent: () => ComponentType<{ pageName?: CloudSecurityPosturePageId }>;
|
||||
/** Gets the `Manage` breadcrumb entry. */
|
||||
getManageBreadcrumbEntry: () => BreadcrumbEntry;
|
||||
getManageBreadcrumbEntry: () => BreadcrumbEntry | undefined;
|
||||
}
|
||||
|
|
|
@ -38,11 +38,6 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
*/
|
||||
responseActionsConsoleEnabled: true,
|
||||
|
||||
/**
|
||||
* Enables the cloud security posture navigation inside the security solution
|
||||
*/
|
||||
cloudSecurityPostureNavigation: false,
|
||||
|
||||
/**
|
||||
* Enables the insights module for related alerts by process ancestry
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { getSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public';
|
||||
import type { LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||
import { getCasesDeepLinks } from '@kbn/cases-plugin/public';
|
||||
import {
|
||||
|
@ -162,6 +163,10 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
|
|||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
...getSecuritySolutionLink<SecurityPageName>('dashboard'),
|
||||
features: [FEATURE.general],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -219,12 +224,18 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...getSecuritySolutionLink<SecurityPageName>('findings'),
|
||||
features: [FEATURE.general],
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9002,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.exploreLanding,
|
||||
title: EXPLORE,
|
||||
path: HOSTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9004,
|
||||
order: 9005,
|
||||
searchable: false,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
|
@ -405,7 +416,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
|
|||
title: TIMELINES,
|
||||
path: TIMELINES_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9002,
|
||||
order: 9003,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.timelines', {
|
||||
|
@ -427,7 +438,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
|
|||
extend: {
|
||||
[SecurityPageName.case]: {
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9003,
|
||||
order: 9004,
|
||||
features: [FEATURE.casesRead],
|
||||
},
|
||||
[SecurityPageName.caseConfigure]: {
|
||||
|
@ -447,7 +458,7 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
|
|||
path: ENDPOINTS_PATH,
|
||||
features: [FEATURE.general],
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9005,
|
||||
order: 9006,
|
||||
searchable: false,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.manage', {
|
||||
|
@ -486,6 +497,10 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
|
|||
title: BLOCKLIST,
|
||||
path: BLOCKLIST_PATH,
|
||||
},
|
||||
{
|
||||
...getSecuritySolutionLink<SecurityPageName>('benchmarks'),
|
||||
deepLinks: [getSecuritySolutionLink<SecurityPageName>('rules')],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.responseActions,
|
||||
title: RESPONSE_ACTIONS,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getSecuritySolutionNavTab } from '@kbn/cloud-security-posture-plugin/public';
|
||||
import * as i18n from '../translations';
|
||||
import type { SecurityNav, SecurityNavGroup } from '../../common/components/navigation/types';
|
||||
import { SecurityNavGroupKey } from '../../common/components/navigation/types';
|
||||
|
@ -31,6 +32,7 @@ import {
|
|||
APP_LANDING_PATH,
|
||||
APP_RESPONSE_ACTIONS_PATH,
|
||||
APP_THREAT_INTELLIGENCE_PATH,
|
||||
APP_PATH,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const navTabs: SecurityNav = {
|
||||
|
@ -181,6 +183,22 @@ export const navTabs: SecurityNav = {
|
|||
disabled: false,
|
||||
urlKey: 'threat_intelligence',
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureFindings]: {
|
||||
...getSecuritySolutionNavTab<SecurityPageName>('findings', APP_PATH),
|
||||
urlKey: 'findings',
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureDashboard]: {
|
||||
...getSecuritySolutionNavTab<SecurityPageName>('dashboard', APP_PATH),
|
||||
urlKey: 'cloud_posture',
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureBenchmarks]: {
|
||||
...getSecuritySolutionNavTab<SecurityPageName>('benchmarks', APP_PATH),
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureRules]: {
|
||||
...getSecuritySolutionNavTab<SecurityPageName>('rules', APP_PATH),
|
||||
urlKey: 'administration',
|
||||
},
|
||||
};
|
||||
|
||||
export const securityNavGroup: SecurityNavGroup = {
|
||||
|
@ -192,6 +210,10 @@ export const securityNavGroup: SecurityNavGroup = {
|
|||
id: SecurityNavGroupKey.detect,
|
||||
name: i18n.DETECT,
|
||||
},
|
||||
[SecurityNavGroupKey.findings]: {
|
||||
id: SecurityNavGroupKey.findings,
|
||||
name: i18n.FINDINGS,
|
||||
},
|
||||
[SecurityNavGroupKey.explore]: {
|
||||
id: SecurityNavGroupKey.explore,
|
||||
name: i18n.EXPLORE,
|
||||
|
|
|
@ -96,6 +96,9 @@ export const HOST_ISOLATION_EXCEPTIONS = i18n.translate(
|
|||
export const DETECT = i18n.translate('xpack.securitySolution.navigation.detect', {
|
||||
defaultMessage: 'Detect',
|
||||
});
|
||||
export const FINDINGS = i18n.translate('xpack.securitySolution.navigation.findings', {
|
||||
defaultMessage: 'Findings',
|
||||
});
|
||||
export const EXPLORE = i18n.translate('xpack.securitySolution.navigation.explore', {
|
||||
defaultMessage: 'Explore',
|
||||
});
|
||||
|
|
|
@ -4,70 +4,56 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
getSecuritySolutionDashboardLinks,
|
||||
getSecuritySolutionManageLinks,
|
||||
getSecuritySolutionRootLinks,
|
||||
} from '@kbn/cloud-security-posture-plugin/public';
|
||||
import { getSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SecurityPageName } from '../../common/constants';
|
||||
import { SecurityPageName, SERVER_APP_ID } from '../../common/constants';
|
||||
import cloudSecurityPostureDashboardImage from '../common/images/cloud_security_posture_dashboard_page.png';
|
||||
import type { LinkCategories, LinkItem } from '../common/links/types';
|
||||
import { IconExceptionLists } from '../management/icons/exception_lists';
|
||||
|
||||
const commonLinkProperties: Partial<LinkItem> = {
|
||||
hideTimeline: true,
|
||||
experimentalKey: 'cloudSecurityPostureNavigation',
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
};
|
||||
|
||||
export const rootLinks: LinkItem = getSecuritySolutionRootLinks<LinkItem>({
|
||||
[SecurityPageName.cloudSecurityPostureFindings]: {
|
||||
globalNavEnabled: true,
|
||||
globalNavOrder: 3,
|
||||
...commonLinkProperties,
|
||||
},
|
||||
});
|
||||
export const rootLinks: LinkItem = {
|
||||
...getSecuritySolutionLink<SecurityPageName>('findings'),
|
||||
globalNavEnabled: true,
|
||||
globalNavOrder: 3,
|
||||
...commonLinkProperties,
|
||||
};
|
||||
|
||||
export const dashboardLinks = getSecuritySolutionDashboardLinks<LinkItem>({
|
||||
[SecurityPageName.cloudSecurityPostureDashboard]: {
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.appLinks.cloudSecurityPostureDashboardDescription',
|
||||
{
|
||||
defaultMessage: 'An overview of findings across all CSP integrations.',
|
||||
}
|
||||
),
|
||||
landingImage: cloudSecurityPostureDashboardImage,
|
||||
// TODO: When CSP is rendered exclusively in the security solution - remove this and rename the title inside the
|
||||
// CSP plugin
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.cloudSecurityPostureDashboard', {
|
||||
defaultMessage: 'Cloud Posture',
|
||||
}),
|
||||
...commonLinkProperties,
|
||||
},
|
||||
});
|
||||
export const dashboardLinks: LinkItem = {
|
||||
...getSecuritySolutionLink<SecurityPageName>('dashboard'),
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.appLinks.cloudSecurityPostureDashboardDescription',
|
||||
{
|
||||
defaultMessage: 'An overview of findings across all CSP integrations.',
|
||||
}
|
||||
),
|
||||
landingImage: cloudSecurityPostureDashboardImage,
|
||||
...commonLinkProperties,
|
||||
};
|
||||
|
||||
export const manageLinks: LinkItem = getSecuritySolutionManageLinks<LinkItem>({
|
||||
[SecurityPageName.cloudSecurityPostureBenchmarks]: {
|
||||
// TODO: When CSP is rendered exclusively in the security solution - remove this and rename the title inside the
|
||||
// CSP plugin
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.cloudSecurityPostureBenchmarks', {
|
||||
defaultMessage: 'CSP Benchmarks',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.appLinks.cloudSecurityPostureBenchmarksDescription',
|
||||
{
|
||||
defaultMessage: 'View, enable, and or disable benchmark rules.',
|
||||
}
|
||||
),
|
||||
landingIcon: IconExceptionLists,
|
||||
...commonLinkProperties,
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureRules]: {
|
||||
sideNavDisabled: true,
|
||||
globalSearchDisabled: true,
|
||||
...commonLinkProperties,
|
||||
},
|
||||
});
|
||||
export const manageLinks: LinkItem = {
|
||||
...getSecuritySolutionLink<SecurityPageName>('benchmarks'),
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.appLinks.cloudSecurityPostureBenchmarksDescription',
|
||||
{
|
||||
defaultMessage: 'View, enable, and or disable benchmark rules.',
|
||||
}
|
||||
),
|
||||
landingIcon: IconExceptionLists,
|
||||
...commonLinkProperties,
|
||||
links: [
|
||||
{
|
||||
...getSecuritySolutionLink<SecurityPageName>('rules'),
|
||||
sideNavDisabled: true,
|
||||
globalSearchDisabled: true,
|
||||
...commonLinkProperties,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const manageCategories: LinkCategories = [
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
type CspSecuritySolutionContext,
|
||||
} from '@kbn/cloud-security-posture-plugin/public';
|
||||
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
|
||||
import { useIsGroupedNavigationEnabled } from '../common/components/navigation/helpers';
|
||||
import { MANAGE_PATH } from '../../common/constants';
|
||||
import type { SecurityPageName, SecuritySubPluginRoutes } from '../app/types';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
@ -27,11 +28,13 @@ const CloudPostureSpyRoute = ({ pageName }: { pageName?: CloudSecurityPosturePag
|
|||
|
||||
const CloudSecurityPosture = memo(() => {
|
||||
const { cloudSecurityPosture } = useKibana().services;
|
||||
const isGroupedNavigationEnabled = useIsGroupedNavigationEnabled();
|
||||
const CloudSecurityPostureRouter = cloudSecurityPosture.getCloudSecurityPostureRouter();
|
||||
const securitySolutionContext: CspSecuritySolutionContext = {
|
||||
getFiltersGlobalComponent: () => FiltersGlobal,
|
||||
getSpyRouteComponent: () => CloudPostureSpyRoute,
|
||||
getManageBreadcrumbEntry: () => ({ name: MANAGE, path: MANAGE_PATH }),
|
||||
getManageBreadcrumbEntry: () =>
|
||||
isGroupedNavigationEnabled ? { name: MANAGE, path: MANAGE_PATH } : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -25,6 +25,7 @@ export interface NavGroupTab {
|
|||
export enum SecurityNavGroupKey {
|
||||
dashboards = 'dashboards',
|
||||
detect = 'detect',
|
||||
findings = 'findings',
|
||||
explore = 'explore',
|
||||
investigate = 'investigate',
|
||||
manage = 'manage',
|
||||
|
@ -46,7 +47,9 @@ export type UrlStateType =
|
|||
| 'timeline'
|
||||
| 'explore'
|
||||
| 'dashboards'
|
||||
| 'threat_intelligence';
|
||||
| 'threat_intelligence'
|
||||
| 'cloud_posture'
|
||||
| 'findings';
|
||||
|
||||
export type SecurityNavGroup = Record<SecurityNavGroupKey, NavGroupTab>;
|
||||
export interface NavTab {
|
||||
|
@ -80,6 +83,10 @@ export const securityNavKeys = [
|
|||
SecurityPageName.users,
|
||||
SecurityPageName.kubernetes,
|
||||
SecurityPageName.threatIntelligence,
|
||||
SecurityPageName.cloudSecurityPostureDashboard,
|
||||
SecurityPageName.cloudSecurityPostureFindings,
|
||||
SecurityPageName.cloudSecurityPostureBenchmarks,
|
||||
SecurityPageName.cloudSecurityPostureRules,
|
||||
] as const;
|
||||
export type SecurityNavKey = typeof securityNavKeys[number];
|
||||
|
||||
|
|
|
@ -44,6 +44,16 @@ Object {
|
|||
"name": "Detection & Response",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cloud_security_posture-dashboard",
|
||||
"data-test-subj": "navigation-cloud_security_posture-dashboard",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cloud_security_posture-dashboard",
|
||||
"id": "cloud_security_posture-dashboard",
|
||||
"isSelected": false,
|
||||
"name": "Cloud Posture",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Dashboards",
|
||||
},
|
||||
|
@ -83,6 +93,22 @@ Object {
|
|||
],
|
||||
"name": "Detect",
|
||||
},
|
||||
Object {
|
||||
"id": "findings",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cloud_security_posture-findings",
|
||||
"data-test-subj": "navigation-cloud_security_posture-findings",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cloud_security_posture-findings",
|
||||
"id": "cloud_security_posture-findings",
|
||||
"isSelected": false,
|
||||
"name": "Findings",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Findings",
|
||||
},
|
||||
Object {
|
||||
"id": "explore",
|
||||
"items": Array [
|
||||
|
@ -208,6 +234,16 @@ Object {
|
|||
"name": "Blocklist",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cloud_security_posture-benchmarks",
|
||||
"data-test-subj": "navigation-cloud_security_posture-benchmarks",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cloud_security_posture-benchmarks",
|
||||
"id": "cloud_security_posture-benchmarks",
|
||||
"isSelected": false,
|
||||
"name": "CSP Benchmarks",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Manage",
|
||||
},
|
||||
|
|
|
@ -100,7 +100,7 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
expect(result?.current?.items?.[3].items?.[2].id).toEqual(SecurityPageName.users);
|
||||
expect(result?.current?.items?.[4].items?.[2].id).toEqual(SecurityPageName.users);
|
||||
});
|
||||
|
||||
// TODO: [kubernetes] remove when no longer experimental
|
||||
|
@ -110,7 +110,7 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
expect(result?.current?.items?.[1].items?.[2].id).toEqual(SecurityPageName.kubernetes);
|
||||
expect(result?.current?.items?.[1].items?.[3].id).toEqual(SecurityPageName.kubernetes);
|
||||
});
|
||||
|
||||
it('should omit host isolation exceptions if hook reports false', () => {
|
||||
|
@ -138,7 +138,7 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
const caseNavItem = (result.current?.items || [])[4].items?.find(
|
||||
const caseNavItem = (result.current?.items || [])[5].items?.find(
|
||||
(item) => item['data-test-subj'] === 'navigation-cases'
|
||||
);
|
||||
expect(caseNavItem).toMatchInlineSnapshot(`
|
||||
|
|
|
@ -87,6 +87,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
|||
items: [
|
||||
navTabs[SecurityPageName.overview],
|
||||
navTabs[SecurityPageName.detectionAndResponse],
|
||||
navTabs[SecurityPageName.cloudSecurityPostureDashboard],
|
||||
...(navTabs[SecurityPageName.kubernetes] != null
|
||||
? [navTabs[SecurityPageName.kubernetes]]
|
||||
: []),
|
||||
|
@ -100,6 +101,10 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
|||
navTabs[SecurityPageName.exceptions],
|
||||
],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.findings],
|
||||
items: [navTabs[SecurityPageName.cloudSecurityPostureFindings]],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.explore],
|
||||
items: [
|
||||
|
@ -128,6 +133,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
|||
? [navTabs[SecurityPageName.hostIsolationExceptions]]
|
||||
: []),
|
||||
navTabs[SecurityPageName.blocklist],
|
||||
navTabs[SecurityPageName.cloudSecurityPostureBenchmarks],
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
@ -22,6 +22,7 @@ const DEPRECATED_HIDDEN_TIMELINE_ROUTES: readonly string[] = [
|
|||
'/explore',
|
||||
'/dashboards',
|
||||
'/manage',
|
||||
'/cloud_security_posture*',
|
||||
];
|
||||
|
||||
const isTimelinePathVisible = (
|
||||
|
|
|
@ -5719,19 +5719,6 @@
|
|||
},
|
||||
"attributesPerMap": {
|
||||
"properties": {
|
||||
"customIconsCount": {
|
||||
"properties": {
|
||||
"min": {
|
||||
"type": "long"
|
||||
},
|
||||
"max": {
|
||||
"type": "long"
|
||||
},
|
||||
"avg": {
|
||||
"type": "float"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourcesCount": {
|
||||
"properties": {
|
||||
"min": {
|
||||
|
@ -5791,6 +5778,19 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"customIconsCount": {
|
||||
"properties": {
|
||||
"min": {
|
||||
"type": "long"
|
||||
},
|
||||
"max": {
|
||||
"type": "long"
|
||||
},
|
||||
"avg": {
|
||||
"type": "float"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10384,10 +10384,6 @@
|
|||
"xpack.csp.benchmarks.totalIntegrationsCountMessage": "Affichage de {pageCount} sur {totalCount, plural, one {# intégration} other {# intégrations}}",
|
||||
"xpack.csp.cspEvaluationBadge.failLabel": "Échec",
|
||||
"xpack.csp.cspEvaluationBadge.passLabel": "Réussite",
|
||||
"xpack.csp.cspHealthBadge.criticalLabel": "Critique",
|
||||
"xpack.csp.cspHealthBadge.healthyLabel": "Intègre",
|
||||
"xpack.csp.cspHealthBadge.warningLabel": "Avertissement",
|
||||
"xpack.csp.cspPageTemplate.navigationTitle": "Niveau de sécurité du cloud",
|
||||
"xpack.csp.cspSettings.rules": "Règles de sécurité du CSP - ",
|
||||
"xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "Section CIS",
|
||||
"xpack.csp.expandColumnDescriptionLabel": "Développer",
|
||||
|
@ -10415,7 +10411,6 @@
|
|||
"xpack.csp.rules.manageIntegrationButtonLabel": "Gérer l'intégration",
|
||||
"xpack.csp.rules.selectAllButtonLabel": "Tout sélectionner",
|
||||
"xpack.csp.rules.tableHeader.lastModificationLabel": "Dernière modification de l'intégration {timeAgo} ",
|
||||
"xpack.csp.unknownRoute.pageNotFoundTitle": "Page introuvable",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard": "Choisir le tableau de bord de destination",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.openInNewTab": "Ouvrir le tableau de bord dans un nouvel onglet",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.useCurrentDateRange": "Utiliser la plage de dates du tableau de bord d'origine",
|
||||
|
|
|
@ -10376,10 +10376,6 @@
|
|||
"xpack.csp.benchmarks.totalIntegrationsCountMessage": "{pageCount}/{totalCount, plural, other {#個の統合}}を表示しています",
|
||||
"xpack.csp.cspEvaluationBadge.failLabel": "失敗",
|
||||
"xpack.csp.cspEvaluationBadge.passLabel": "合格",
|
||||
"xpack.csp.cspHealthBadge.criticalLabel": "重大",
|
||||
"xpack.csp.cspHealthBadge.healthyLabel": "正常",
|
||||
"xpack.csp.cspHealthBadge.warningLabel": "警告",
|
||||
"xpack.csp.cspPageTemplate.navigationTitle": "クラウドセキュリティ態勢",
|
||||
"xpack.csp.cspSettings.rules": "CSPセキュリティルール - ",
|
||||
"xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "CISセクション",
|
||||
"xpack.csp.expandColumnDescriptionLabel": "拡張",
|
||||
|
@ -10407,7 +10403,6 @@
|
|||
"xpack.csp.rules.manageIntegrationButtonLabel": "統合を管理",
|
||||
"xpack.csp.rules.selectAllButtonLabel": "すべて選択",
|
||||
"xpack.csp.rules.tableHeader.lastModificationLabel": "統合{timeAgo}の前回変更日 ",
|
||||
"xpack.csp.unknownRoute.pageNotFoundTitle": "ページが見つかりません",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard": "対象ダッシュボードを選択",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.openInNewTab": "新しいタブでダッシュボードを開く",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.useCurrentDateRange": "元のダッシュボードから日付範囲を使用",
|
||||
|
|
|
@ -10390,10 +10390,6 @@
|
|||
"xpack.csp.benchmarks.totalIntegrationsCountMessage": "正在显示 {pageCount}/{totalCount, plural, other {# 个集成}}",
|
||||
"xpack.csp.cspEvaluationBadge.failLabel": "失败",
|
||||
"xpack.csp.cspEvaluationBadge.passLabel": "通过",
|
||||
"xpack.csp.cspHealthBadge.criticalLabel": "紧急",
|
||||
"xpack.csp.cspHealthBadge.healthyLabel": "运行正常",
|
||||
"xpack.csp.cspHealthBadge.warningLabel": "警告",
|
||||
"xpack.csp.cspPageTemplate.navigationTitle": "云安全态势",
|
||||
"xpack.csp.cspSettings.rules": "CSP 安全规则 - ",
|
||||
"xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "CIS 部分",
|
||||
"xpack.csp.expandColumnDescriptionLabel": "展开",
|
||||
|
@ -10421,7 +10417,6 @@
|
|||
"xpack.csp.rules.manageIntegrationButtonLabel": "管理集成",
|
||||
"xpack.csp.rules.selectAllButtonLabel": "全选",
|
||||
"xpack.csp.rules.tableHeader.lastModificationLabel": "上次修改集成 {timeAgo} ",
|
||||
"xpack.csp.unknownRoute.pageNotFoundTitle": "未找到页面",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard": "选择目标仪表板",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.openInNewTab": "在新选项卡中打开仪表板",
|
||||
"xpack.dashboard.components.DashboardDrilldownConfig.useCurrentDateRange": "使用源仪表板的日期范围",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue