mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[SecuritySolution] Migrate ESS get started page to ess_security plugin (#159603)
## Summary Original issue: https://github.com/elastic/security-team/issues/6489 Relevant issue: https://github.com/elastic/kibana/pull/158461 | https://github.com/elastic/kibana/pull/158955 1. Move Landing Cards Component to ess_security plugin 2. Any component render `LandingPageComponent` when no indices exists will be redirected to get started page. **Steps to verify:** 1. Start an empty Elastic Search to verify the get started page, then visit all the pages that render get started page when no indices exist to see if they are rendered properly. 2. Add some data to Elastic Search and visit each page and see if it's rendered properly. Open the timeline, and flyout, make sure they are looking alright on the page. ESS `yarn start`: Get started page: No data view exists: <img width="2549" alt="ess-get-started" src="dd560ab3
-e3b1-4695-adfa-a1851da04cb0"> Serverless `yarn serverless-security`: Get started page: <img width="2553" alt="serverless-get-started" src="2839c66f
-e675-4eb5-b909-1b7af450b6d1"> No data view exists: <img width="2542" alt="serverless-no-data" src="d8e3a26b
-3d39-442a-98ca-df0831e332ea"> Data exists: <img width="2539" alt="Screenshot 2023-06-14 at 23 30 34" src="ab7b413e
-f25a-4c5e-9946-f4094a26fcdb"> ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Sergi Massaneda <sergi.massaneda@gmail.com>
This commit is contained in:
parent
c990bea80c
commit
0e67ae364c
49 changed files with 592 additions and 342 deletions
|
@ -74,6 +74,7 @@
|
|||
"xpack.serverlessObservability": "plugins/serverless_observability",
|
||||
"xpack.serverlessSecurity": "plugins/serverless_security",
|
||||
"xpack.securitySolution": "plugins/security_solution",
|
||||
"xpack.securitySolutionEss": "plugins/ess_security",
|
||||
"xpack.sessionView": "plugins/session_view",
|
||||
"xpack.snapshotRestore": "plugins/snapshot_restore",
|
||||
"xpack.spaces": "plugins/spaces",
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
"browser": true,
|
||||
"configPath": ["xpack", "ess", "security"],
|
||||
"requiredPlugins": [
|
||||
"securitySolution",
|
||||
"securitySolution"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": []
|
||||
"optionalPlugins": [
|
||||
"cloudExperiments",
|
||||
],
|
||||
"requiredBundles": [ "kibanaReact"]
|
||||
}
|
||||
}
|
||||
|
|
23
x-pack/plugins/ess_security/public/__mocks__/services.tsx
Normal file
23
x-pack/plugins/ess_security/public/__mocks__/services.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const KibanaServicesProvider = jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => <div>{children}</div>);
|
||||
|
||||
export const useKibana = jest.fn().mockReturnValue({
|
||||
services: {
|
||||
http: {
|
||||
basePath: {
|
||||
prepend: jest.fn().mockImplementation((path: string) => path),
|
||||
},
|
||||
},
|
||||
cloudExperiments: {},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const useVariation = jest.fn();
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks';
|
||||
import { useVariation } from './use_variation';
|
||||
|
||||
describe('useVariation', () => {
|
||||
test('it should call the setter if cloudExperiments is enabled', async () => {
|
||||
const cloudExperiments = cloudExperimentsMock.createStartMock();
|
||||
cloudExperiments.getVariation.mockResolvedValue('resolved value');
|
||||
const setter = jest.fn();
|
||||
const { result } = renderHook(() =>
|
||||
useVariation(
|
||||
cloudExperiments,
|
||||
'security-solutions.add-integrations-url',
|
||||
'my default value',
|
||||
setter
|
||||
)
|
||||
);
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
expect(result.error).toBe(undefined);
|
||||
expect(setter).toHaveBeenCalledTimes(1);
|
||||
expect(setter).toHaveBeenCalledWith('resolved value');
|
||||
});
|
||||
|
||||
test('it should not call the setter if cloudExperiments is not enabled', async () => {
|
||||
const setter = jest.fn();
|
||||
const { result } = renderHook(() =>
|
||||
useVariation(undefined, 'security-solutions.add-integrations-url', 'my default value', setter)
|
||||
);
|
||||
await new Promise((resolve) => process.nextTick(resolve));
|
||||
expect(result.error).toBe(undefined);
|
||||
expect(setter).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { useEffect } from 'react';
|
||||
import type {
|
||||
CloudExperimentsFeatureFlagNames,
|
||||
CloudExperimentsPluginStart,
|
||||
} from '@kbn/cloud-experiments-plugin/common';
|
||||
/**
|
||||
* Retrieves the variation of the feature flag if the cloudExperiments plugin is enabled.
|
||||
* @param cloudExperiments {@link CloudExperimentsPluginStart}
|
||||
* @param featureFlagName The name of the feature flag {@link CloudExperimentsFeatureFlagNames}
|
||||
* @param defaultValue The default value in case it cannot retrieve the feature flag
|
||||
* @param setter The setter from {@link useState} to update the value.
|
||||
*/
|
||||
export const useVariation = <Data>(
|
||||
cloudExperiments: CloudExperimentsPluginStart | undefined,
|
||||
featureFlagName: CloudExperimentsFeatureFlagNames,
|
||||
defaultValue: Data,
|
||||
setter: (value: Data) => void
|
||||
) => {
|
||||
useEffect(() => {
|
||||
(async function loadVariation() {
|
||||
const variationUrl = await cloudExperiments?.getVariation(featureFlagName, defaultValue);
|
||||
if (variationUrl) {
|
||||
setter(variationUrl);
|
||||
}
|
||||
})();
|
||||
}, [cloudExperiments, featureFlagName, defaultValue, setter]);
|
||||
};
|
26
x-pack/plugins/ess_security/public/common/jest.config.js
Normal file
26
x-pack/plugins/ess_security/public/common/jest.config.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/ess_security/public/common'],
|
||||
testMatch: ['<rootDir>/x-pack/plugins/ess_security/public/common/**/*.test.{js,mjs,ts,tsx}'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/ess_security/public/common',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/plugins/ess_security/public/common/**/*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/common/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/common/*mock*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.d.ts',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/common/*.config.ts',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/common/index.{js,ts,tsx}',
|
||||
],
|
||||
};
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 281 KiB |
Before Width: | Height: | Size: 513 KiB After Width: | Height: | Size: 513 KiB |
21
x-pack/plugins/ess_security/public/get_started/index.tsx
Normal file
21
x-pack/plugins/ess_security/public/get_started/index.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { CoreStart } from '@kbn/core/public';
|
||||
import { KibanaServicesProvider } from '../services';
|
||||
import { EssSecurityPluginStartDependencies } from '../types';
|
||||
import { GetStarted } from './lazy';
|
||||
|
||||
export const getSecurityGetStartedComponent =
|
||||
(core: CoreStart, pluginsStart: EssSecurityPluginStartDependencies): React.ComponentType =>
|
||||
() =>
|
||||
(
|
||||
<KibanaServicesProvider core={core} pluginsStart={pluginsStart}>
|
||||
<GetStarted />
|
||||
</KibanaServicesProvider>
|
||||
);
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/ess_security/public/get_started'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/ess_security/public/get_started',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: ['<rootDir>/x-pack/plugins/ess_security/public/get_started/**/*.{ts,tsx}'],
|
||||
};
|
|
@ -7,49 +7,39 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { useVariationMock } from '../utils.mocks';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { LandingCards } from '.';
|
||||
import { ADD_DATA_PATH } from '../../../../common/constants';
|
||||
import { LandingCards } from './landing_cards';
|
||||
import { ADD_DATA_PATH } from '@kbn/security-solution-plugin/common';
|
||||
import { useVariation } from '../common/hooks/use_variation';
|
||||
|
||||
jest.mock('../common/hooks/use_variation');
|
||||
jest.mock('../services');
|
||||
|
||||
describe('LandingCards component', () => {
|
||||
beforeEach(() => {
|
||||
useVariationMock.mockReset();
|
||||
(useVariation as jest.Mock).mockReset();
|
||||
});
|
||||
|
||||
it('has add data links', () => {
|
||||
const { getAllByText } = render(
|
||||
<TestProviders>
|
||||
<LandingCards />
|
||||
</TestProviders>
|
||||
);
|
||||
const { getAllByText } = render(<LandingCards />);
|
||||
expect(getAllByText('Add security integrations')).toHaveLength(2);
|
||||
});
|
||||
|
||||
describe.each(['header', 'footer'])('URLs at the %s', (place) => {
|
||||
it('points to the default Add data URL', () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<LandingCards />
|
||||
</TestProviders>
|
||||
);
|
||||
const { queryByTestId } = render(<LandingCards />);
|
||||
const link = queryByTestId(`add-integrations-${place}`);
|
||||
expect(link?.getAttribute('href')).toBe(ADD_DATA_PATH);
|
||||
});
|
||||
|
||||
it('points to the resolved Add data URL by useVariation', () => {
|
||||
const customResolvedUrl = '/test/url';
|
||||
useVariationMock.mockImplementationOnce(
|
||||
(useVariation as jest.Mock).mockImplementationOnce(
|
||||
(cloudExperiments, featureFlagName, defaultValue, setter) => {
|
||||
setter(customResolvedUrl);
|
||||
}
|
||||
);
|
||||
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders>
|
||||
<LandingCards />
|
||||
</TestProviders>
|
||||
);
|
||||
const { queryByTestId } = render(<LandingCards />);
|
||||
const link = queryByTestId(`add-integrations-${place}`);
|
||||
expect(link?.getAttribute('href')).toBe(customResolvedUrl);
|
||||
});
|
|
@ -6,15 +6,23 @@
|
|||
*/
|
||||
|
||||
import React, { memo, useMemo, useState } from 'react';
|
||||
import { EuiButton, EuiCard, EuiFlexGroup, EuiFlexItem, EuiPageHeader } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { useVariation } from '../utils';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCard,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPageHeader,
|
||||
EuiThemeComputed,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { ADD_DATA_PATH } from '@kbn/security-solution-plugin/common';
|
||||
import { useVariation } from '../common/hooks/use_variation';
|
||||
import * as i18n from './translations';
|
||||
import endpointSvg from '../../images/endpoint1.svg';
|
||||
import cloudSvg from '../../images/cloud1.svg';
|
||||
import siemSvg from '../../images/siem1.svg';
|
||||
import { ADD_DATA_PATH } from '../../../../common/constants';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import endpointSvg from './images/endpoint1.svg';
|
||||
import cloudSvg from './images/cloud1.svg';
|
||||
import siemSvg from './images/siem1.svg';
|
||||
import { useKibana } from '../services';
|
||||
|
||||
const imgUrls = {
|
||||
cloud: cloudSvg,
|
||||
|
@ -22,13 +30,32 @@ const imgUrls = {
|
|||
endpoint: endpointSvg,
|
||||
};
|
||||
|
||||
const StyledEuiCard = styled(EuiCard)`
|
||||
const headerCardStyles = css`
|
||||
span.euiTitle {
|
||||
font-size: 36px;
|
||||
line-height: 100%;
|
||||
}
|
||||
`;
|
||||
const StyledEuiCardTop = styled(EuiCard)`
|
||||
|
||||
const pageHeaderStyles = css`
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
const getFlexItemStyles = (euiTheme: EuiThemeComputed) => css`
|
||||
background: ${euiTheme.colors.lightestShade};
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
const cardStyles = css`
|
||||
img {
|
||||
margin-top: 20px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
|
||||
const footerStyles = css`
|
||||
span.euiTitle {
|
||||
font-size: 36px;
|
||||
line-height: 100%;
|
||||
|
@ -37,24 +64,6 @@ const StyledEuiCardTop = styled(EuiCard)`
|
|||
display: block;
|
||||
margin: 20px auto 0;
|
||||
`;
|
||||
const StyledEuiPageHeader = styled(EuiPageHeader)`
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledImgEuiCard = styled(EuiCard)`
|
||||
img {
|
||||
margin-top: 20px;
|
||||
max-width: 400px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEuiFlexItem = styled(EuiFlexItem)`
|
||||
background: ${({ theme }) => theme.eui.euiColorLightestShade};
|
||||
padding: 20px;
|
||||
margin: -12px !important;
|
||||
`;
|
||||
|
||||
export const LandingCards = memo(() => {
|
||||
const {
|
||||
|
@ -64,6 +73,7 @@ export const LandingCards = memo(() => {
|
|||
cloudExperiments,
|
||||
} = useKibana().services;
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [addIntegrationsUrl, setAddIntegrationsUrl] = useState(ADD_DATA_PATH);
|
||||
useVariation(
|
||||
cloudExperiments,
|
||||
|
@ -73,13 +83,18 @@ export const LandingCards = memo(() => {
|
|||
);
|
||||
|
||||
const href = useMemo(() => prepend(addIntegrationsUrl), [prepend, addIntegrationsUrl]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup data-test-subj="siem-landing-page" direction="column" gutterSize="l">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup data-test-subj="siem-landing-page" direction="column" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="l">
|
||||
<EuiFlexItem>
|
||||
<StyledEuiPageHeader pageTitle={i18n.SIEM_HEADER} iconType="logoSecurity" />
|
||||
<StyledEuiCard
|
||||
<EuiPageHeader
|
||||
pageTitle={i18n.SIEM_HEADER}
|
||||
iconType="logoSecurity"
|
||||
css={pageHeaderStyles}
|
||||
/>
|
||||
<EuiCard
|
||||
display="plain"
|
||||
description={i18n.SIEM_DESCRIPTION}
|
||||
textAlign="left"
|
||||
|
@ -89,6 +104,7 @@ export const LandingCards = memo(() => {
|
|||
{i18n.SIEM_CTA}
|
||||
</EuiButton>
|
||||
}
|
||||
css={headerCardStyles}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -107,39 +123,42 @@ export const LandingCards = memo(() => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<StyledEuiFlexItem>
|
||||
<EuiFlexItem css={getFlexItemStyles(euiTheme)}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<StyledImgEuiCard
|
||||
<EuiCard
|
||||
hasBorder
|
||||
description={i18n.SIEM_CARD_DESCRIPTION}
|
||||
image={imgUrls.siem}
|
||||
textAlign="center"
|
||||
title={i18n.SIEM_CARD_TITLE}
|
||||
css={cardStyles}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<StyledImgEuiCard
|
||||
<EuiCard
|
||||
hasBorder
|
||||
description={i18n.ENDPOINT_DESCRIPTION}
|
||||
image={imgUrls.endpoint}
|
||||
textAlign="center"
|
||||
title={i18n.ENDPOINT_TITLE}
|
||||
css={cardStyles}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<StyledImgEuiCard
|
||||
<EuiCard
|
||||
hasBorder
|
||||
description={i18n.CLOUD_CARD_DESCRIPTION}
|
||||
image={imgUrls.cloud}
|
||||
textAlign="center"
|
||||
title={i18n.CLOUD_CARD_TITLE}
|
||||
css={cardStyles}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</StyledEuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<StyledEuiCardTop
|
||||
<EuiCard
|
||||
display="plain"
|
||||
description={i18n.UNIFY_DESCRIPTION}
|
||||
paddingSize="l"
|
||||
|
@ -150,9 +169,13 @@ export const LandingCards = memo(() => {
|
|||
{i18n.SIEM_CTA}
|
||||
</EuiButton>
|
||||
}
|
||||
css={footerStyles}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
LandingCards.displayName = 'LandingCards';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default LandingCards;
|
18
x-pack/plugins/ess_security/public/get_started/lazy.tsx
Normal file
18
x-pack/plugins/ess_security/public/get_started/lazy.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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, { lazy, Suspense } from 'react';
|
||||
import { EuiLoadingLogo } from '@elastic/eui';
|
||||
|
||||
const LandingCardsLazy = lazy(() => import('./landing_cards'));
|
||||
|
||||
const centerLogoStyle = { display: 'flex', margin: 'auto' };
|
||||
|
||||
export const GetStarted = () => (
|
||||
<Suspense fallback={<EuiLoadingLogo logo="logoSecurity" size="xl" style={centerLogoStyle} />}>
|
||||
<LandingCardsLazy />
|
||||
</Suspense>
|
||||
);
|
|
@ -8,79 +8,79 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const SIEM_HEADER = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.siem.header',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.header',
|
||||
{
|
||||
defaultMessage: 'Elastic Security',
|
||||
}
|
||||
);
|
||||
|
||||
export const SIEM_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.siem.title',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.title',
|
||||
{
|
||||
defaultMessage: 'Security at the speed of Elastic',
|
||||
}
|
||||
);
|
||||
export const SIEM_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.siem.desc',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.desc',
|
||||
{
|
||||
defaultMessage:
|
||||
'Elastic Security equips teams to prevent, detect, and respond to threats at cloud speed and scale — securing business operations with a unified, open platform.',
|
||||
}
|
||||
);
|
||||
export const SIEM_CTA = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.siem.cta',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.siem.cta',
|
||||
{
|
||||
defaultMessage: 'Add security integrations',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENDPOINT_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.endpoint.title',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.endpoint.title',
|
||||
{
|
||||
defaultMessage: 'Endpoint security at scale',
|
||||
}
|
||||
);
|
||||
export const ENDPOINT_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.endpoint.desc',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.endpoint.desc',
|
||||
{
|
||||
defaultMessage: 'Prevent, collect, detect and respond — all with Elastic Agent.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SIEM_CARD_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.siemCard.title',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.siemCard.title',
|
||||
{
|
||||
defaultMessage: 'SIEM for the modern SOC',
|
||||
}
|
||||
);
|
||||
export const SIEM_CARD_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.siemCard.desc',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.siemCard.desc',
|
||||
{
|
||||
defaultMessage: 'Detect, investigate, and respond to evolving threats in your environment.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLOUD_CARD_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.cloudCard.title',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.cloudCard.title',
|
||||
{
|
||||
defaultMessage: 'Cloud protection end-to-end',
|
||||
}
|
||||
);
|
||||
export const CLOUD_CARD_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.cloudCard.desc',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.cloudCard.desc',
|
||||
{
|
||||
defaultMessage: 'Assess your Cloud Posture and protect your workloads from attacks.',
|
||||
}
|
||||
);
|
||||
|
||||
export const UNIFY_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.unify.title',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.unify.title',
|
||||
{
|
||||
defaultMessage: 'Unify SIEM, endpoint security, and cloud security',
|
||||
}
|
||||
);
|
||||
export const UNIFY_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.overview.landingCards.box.unify.desc',
|
||||
'xpack.securitySolutionEss.getStarted.landingCards.box.unify.desc',
|
||||
{
|
||||
defaultMessage:
|
||||
'Elastic Security modernizes security operations — enabling analytics across years of data, automating key processes, and protecting every host.',
|
25
x-pack/plugins/ess_security/public/jest.config.js
Normal file
25
x-pack/plugins/ess_security/public/jest.config.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../..',
|
||||
/** all nested directories have their own Jest config file */
|
||||
testMatch: ['<rootDir>/x-pack/plugins/ess_security/public/*.test.{js,mjs,ts,tsx}'],
|
||||
roots: ['<rootDir>/x-pack/plugins/ess_security/public'],
|
||||
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/ess_security/public',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/plugins/ess_security/public/**/*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/*mock*.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/*.test.{ts,tsx}',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/*.d.ts',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/*.config.ts',
|
||||
'!<rootDir>/x-pack/plugins/ess_security/public/index.{js,ts,tsx}',
|
||||
],
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||
import { getSecurityGetStartedComponent } from './get_started';
|
||||
import {
|
||||
EssSecurityPluginSetup,
|
||||
EssSecurityPluginStart,
|
||||
|
@ -25,16 +26,19 @@ export class EssSecurityPlugin
|
|||
constructor() {}
|
||||
|
||||
public setup(
|
||||
_core: CoreSetup,
|
||||
_setupDeps: EssSecurityPluginSetupDependencies
|
||||
core: CoreSetup,
|
||||
setupDeps: EssSecurityPluginSetupDependencies
|
||||
): EssSecurityPluginSetup {
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(
|
||||
_core: CoreStart,
|
||||
_startDeps: EssSecurityPluginStartDependencies
|
||||
core: CoreStart,
|
||||
startDeps: EssSecurityPluginStartDependencies
|
||||
): EssSecurityPluginStart {
|
||||
const { securitySolution } = startDeps;
|
||||
securitySolution.setGetStartedPage(getSecurityGetStartedComponent(core, startDeps));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
26
x-pack/plugins/ess_security/public/services.tsx
Normal file
26
x-pack/plugins/ess_security/public/services.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { CoreStart } from '@kbn/core/public';
|
||||
import React from 'react';
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
useKibana as useKibanaReact,
|
||||
} from '@kbn/kibana-react-plugin/public';
|
||||
import type { EssSecurityPluginStartDependencies } from './types';
|
||||
|
||||
export type Services = CoreStart & EssSecurityPluginStartDependencies;
|
||||
|
||||
export const KibanaServicesProvider: React.FC<{
|
||||
core: CoreStart;
|
||||
pluginsStart: EssSecurityPluginStartDependencies;
|
||||
}> = ({ core, pluginsStart, children }) => {
|
||||
const services: Services = { ...core, ...pluginsStart };
|
||||
return <KibanaContextProvider services={services}>{children}</KibanaContextProvider>;
|
||||
};
|
||||
|
||||
export const useKibana = () => useKibanaReact<Services>();
|
|
@ -9,6 +9,7 @@ import type {
|
|||
PluginSetup as SecuritySolutionPluginSetup,
|
||||
PluginStart as SecuritySolutionPluginStart,
|
||||
} from '@kbn/security-solution-plugin/public';
|
||||
import { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface EssSecurityPluginSetup {}
|
||||
|
@ -22,4 +23,5 @@ export interface EssSecurityPluginSetupDependencies {
|
|||
|
||||
export interface EssSecurityPluginStartDependencies {
|
||||
securitySolution: SecuritySolutionPluginStart;
|
||||
cloudExperiments?: CloudExperimentsPluginStart;
|
||||
}
|
||||
|
|
|
@ -18,5 +18,8 @@
|
|||
"@kbn/core",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/security-solution-plugin",
|
||||
"@kbn/i18n",
|
||||
"@kbn/cloud-experiments-plugin",
|
||||
"@kbn/kibana-react-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,7 +8,14 @@
|
|||
// TODO(jbudz): should be removed when upgrading to TS@4.8
|
||||
// this is a skip for the errors created when typechecking with isolatedModules
|
||||
export {};
|
||||
export { APP_UI_ID, APP_ID, CASES_FEATURE_ID, SERVER_APP_ID, SecurityPageName } from './constants';
|
||||
export {
|
||||
APP_UI_ID,
|
||||
APP_ID,
|
||||
CASES_FEATURE_ID,
|
||||
SERVER_APP_ID,
|
||||
ADD_DATA_PATH,
|
||||
SecurityPageName,
|
||||
} from './constants';
|
||||
export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants';
|
||||
export { allowedExperimentalValues, type ExperimentalFeatures } from './experimental_features';
|
||||
export type { AppFeatureKeys } from './types/app_features';
|
||||
|
|
|
@ -30,7 +30,9 @@ export const SecuritySolutionBottomBar = React.memo(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export const SecuritySolutionBottomBarProps: EuiBottomBarProps = {
|
||||
export const SecuritySolutionBottomBarProps: EuiBottomBarProps & {
|
||||
restrictWidth?: boolean | number | string;
|
||||
} = {
|
||||
className: BOTTOM_BAR_CLASSNAME,
|
||||
'data-test-subj': 'timeline-bottom-bar-container',
|
||||
};
|
||||
|
|
|
@ -24,11 +24,8 @@ import {
|
|||
SecuritySolutionBottomBarProps,
|
||||
} from './bottom_bar';
|
||||
import { useShowTimeline } from '../../../common/utils/timeline/use_show_timeline';
|
||||
import { useShowPagesWithEmptyView } from '../../../common/utils/empty_view/use_show_pages_with_empty_view';
|
||||
import { useSyncFlyoutStateWithUrl } from '../../../flyout/url/use_sync_flyout_state_with_url';
|
||||
|
||||
const NO_DATA_PAGE_MAX_WIDTH = 950;
|
||||
|
||||
/**
|
||||
* Need to apply the styles via a className to effect the containing bottom bar
|
||||
* rather than applying them to the timeline bar directly
|
||||
|
@ -68,8 +65,6 @@ export const SecuritySolutionTemplateWrapper: React.FC<Omit<KibanaPageTemplatePr
|
|||
// To keep the mode in sync, we pass in the globalColorMode to the bottom bar here
|
||||
const { colorMode: globalColorMode } = useEuiTheme();
|
||||
|
||||
const showEmptyState = useShowPagesWithEmptyView() || rest.isEmptyState;
|
||||
|
||||
const [flyoutRef, handleFlyoutChangedOrClosed] = useSyncFlyoutStateWithUrl();
|
||||
|
||||
/*
|
||||
|
@ -88,16 +83,17 @@ export const SecuritySolutionTemplateWrapper: React.FC<Omit<KibanaPageTemplatePr
|
|||
$isShowingTimelineOverlay={isShowingTimelineOverlay}
|
||||
paddingSize="none"
|
||||
solutionNav={solutionNavProps}
|
||||
restrictWidth={showEmptyState ? NO_DATA_PAGE_MAX_WIDTH : false}
|
||||
restrictWidth={false}
|
||||
{...rest}
|
||||
>
|
||||
<GlobalKQLHeader />
|
||||
<KibanaPageTemplate.Section
|
||||
className="securityPageWrapper"
|
||||
data-test-subj="pageContainer"
|
||||
paddingSize="l"
|
||||
alignment={showEmptyState ? 'center' : 'top'}
|
||||
paddingSize={rest.paddingSize ?? 'l'}
|
||||
alignment="top"
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
{children}
|
||||
</KibanaPageTemplate.Section>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
export const LandingPageComponent = () => <div data-test-subj="siem-landing-page" />;
|
|
@ -1,7 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LandingPageComponent component renders page properly 1`] = `
|
||||
<Memo(SecuritySolutionPageWrapperComponent)>
|
||||
<LandingCards />
|
||||
</Memo(SecuritySolutionPageWrapperComponent)>
|
||||
`;
|
|
@ -4,15 +4,54 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { LandingPageComponent } from '.';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { TestProviders } from '../../mock/test_providers';
|
||||
|
||||
describe('LandingPageComponent component', () => {
|
||||
it('renders page properly', () => {
|
||||
const EmptyComponent = shallow(<LandingPageComponent />);
|
||||
expect(EmptyComponent).toMatchSnapshot();
|
||||
jest.mock('../../lib/kibana', () => ({
|
||||
useKibana: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-use/lib/useObservable', () => jest.fn((fn) => fn()));
|
||||
|
||||
describe('LandingPageComponent', () => {
|
||||
const mockGetStartedComponent = jest.fn();
|
||||
const history = createBrowserHistory();
|
||||
const mockSecuritySolutionTemplateWrapper = jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => <div>{children}</div>);
|
||||
|
||||
const renderPage = () =>
|
||||
render(
|
||||
<Router history={history}>
|
||||
<LandingPageComponent />
|
||||
</Router>,
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
beforeAll(() => {
|
||||
(useKibana as jest.Mock).mockReturnValue({
|
||||
services: {
|
||||
securityLayout: {
|
||||
getPluginWrapper: jest.fn().mockReturnValue(mockSecuritySolutionTemplateWrapper),
|
||||
},
|
||||
getStartedComponent$: mockGetStartedComponent,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the get started component', () => {
|
||||
mockGetStartedComponent.mockReturnValue(<div data-test-subj="get-started" />);
|
||||
const { queryByTestId } = renderPage();
|
||||
|
||||
expect(queryByTestId('get-started')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,15 +6,13 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { LandingCards } from '../landing_cards';
|
||||
import { SecuritySolutionPageWrapper } from '../page_wrapper';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
|
||||
export const LandingPageComponent = memo(() => {
|
||||
return (
|
||||
<SecuritySolutionPageWrapper>
|
||||
<LandingCards />
|
||||
</SecuritySolutionPageWrapper>
|
||||
);
|
||||
const { getStartedComponent$ } = useKibana().services;
|
||||
const GetStartedComponent = useObservable(getStartedComponent$);
|
||||
return <>{GetStartedComponent}</>;
|
||||
});
|
||||
|
||||
LandingPageComponent.displayName = 'LandingPageComponent';
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SecuritySolutionPageWrapper it renders 1`] = `
|
||||
<Memo(SecuritySolutionPageWrapperComponent)>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</Memo(SecuritySolutionPageWrapperComponent)>
|
||||
`;
|
|
@ -5,22 +5,100 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../mock';
|
||||
import { render } from '@testing-library/react';
|
||||
import { SecuritySolutionPageWrapper } from '.';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
|
||||
import { TestProviders } from '../../mock';
|
||||
|
||||
jest.mock('../../hooks/use_experimental_features', () => ({
|
||||
useIsExperimentalFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('SecuritySolutionPageWrapper', () => {
|
||||
test('it renders', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<SecuritySolutionPageWrapper>
|
||||
<p>{'Test page'}</p>
|
||||
</SecuritySolutionPageWrapper>
|
||||
</TestProviders>
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should render children and apply classNames correctly', () => {
|
||||
const { container } = render(
|
||||
<SecuritySolutionPageWrapper
|
||||
className="custom-class"
|
||||
style={{ color: 'red' }}
|
||||
noPadding
|
||||
noTimeline
|
||||
>
|
||||
<div>{'Child Component'}</div>
|
||||
</SecuritySolutionPageWrapper>,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
expect(wrapper.find('Memo(SecuritySolutionPageWrapperComponent)')).toMatchSnapshot();
|
||||
const wrapperElement = container.firstChild;
|
||||
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper--noPadding');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--withTimeline');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
|
||||
expect(wrapperElement).toHaveClass('custom-class');
|
||||
expect(wrapperElement).toHaveStyle('color: red');
|
||||
|
||||
expect(wrapperElement).toContainHTML('<div>Child Component</div>');
|
||||
});
|
||||
|
||||
it('should apply "noPadding" class when "noPadding" prop is true', () => {
|
||||
const { container } = render(
|
||||
<SecuritySolutionPageWrapper noPadding>
|
||||
<div>{'Child Component'}</div>
|
||||
</SecuritySolutionPageWrapper>,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
const wrapperElement = container.firstChild;
|
||||
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper--noPadding');
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper--withTimeline');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
|
||||
});
|
||||
|
||||
it('should apply "noTimeline" class when "noTimeline" prop is true', () => {
|
||||
const { container } = render(
|
||||
<SecuritySolutionPageWrapper noTimeline>
|
||||
<div>{'Child Component'}</div>
|
||||
</SecuritySolutionPageWrapper>,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
const wrapperElement = container.firstChild;
|
||||
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--noPadding');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--withTimeline');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
|
||||
});
|
||||
|
||||
it('should not apply "noTimeline" class when "noTimeline" prop is false', () => {
|
||||
const { container } = render(
|
||||
<SecuritySolutionPageWrapper noTimeline={false}>
|
||||
<div>{'Child Component'}</div>
|
||||
</SecuritySolutionPageWrapper>,
|
||||
{
|
||||
wrapper: TestProviders,
|
||||
}
|
||||
);
|
||||
|
||||
const wrapperElement = container.firstChild;
|
||||
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--noPadding');
|
||||
expect(wrapperElement).toHaveClass('securitySolutionWrapper--withTimeline');
|
||||
expect(wrapperElement).not.toHaveClass('securitySolutionWrapper--fullHeight');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,7 +34,6 @@ Wrapper.displayName = 'Wrapper';
|
|||
|
||||
interface SecuritySolutionPageWrapperProps {
|
||||
children: React.ReactNode;
|
||||
restrictWidth?: boolean | number | string;
|
||||
style?: Record<string, string>;
|
||||
noPadding?: boolean;
|
||||
noTimeline?: boolean;
|
||||
|
|
|
@ -1,53 +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 { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { useShowPagesWithEmptyView } from './use_show_pages_with_empty_view';
|
||||
|
||||
jest.mock('../route/use_route_spy', () => ({
|
||||
useRouteSpy: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => [{ pageName: 'hosts' }])
|
||||
.mockImplementationOnce(() => [{ pageName: 'rules' }])
|
||||
.mockImplementationOnce(() => [{ pageName: 'network' }]),
|
||||
}));
|
||||
jest.mock('../../containers/sourcerer', () => ({
|
||||
useSourcererDataView: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => [{ indicesExist: false }])
|
||||
.mockImplementationOnce(() => [{ indicesExist: false }])
|
||||
.mockImplementationOnce(() => [{ indicesExist: true }])
|
||||
.mockImplementationOnce(() => [{ indicesExist: false }])
|
||||
.mockImplementationOnce(() => [{ indicesExist: true }]),
|
||||
}));
|
||||
|
||||
describe('use show pages with empty view', () => {
|
||||
it('shows empty view when on an elligible page and indices do not exist', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
|
||||
await waitForNextUpdate();
|
||||
const emptyResult = result.current;
|
||||
expect(emptyResult).toEqual(true);
|
||||
});
|
||||
});
|
||||
it('does not show empty view when on an inelligible page and indices do not exist', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
|
||||
await waitForNextUpdate();
|
||||
const emptyResult = result.current;
|
||||
expect(emptyResult).toEqual(false);
|
||||
});
|
||||
});
|
||||
it('shows empty view when on an elligible page and indices do exist', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView());
|
||||
await waitForNextUpdate();
|
||||
const emptyResult = result.current;
|
||||
expect(emptyResult).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,42 +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 { useState, useEffect } from 'react';
|
||||
import { useRouteSpy } from '../route/use_route_spy';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { useSourcererDataView } from '../../containers/sourcerer';
|
||||
|
||||
// Used to detect if we're on a top level page that is empty and set page background color to match the subdued Empty State
|
||||
const isPageNameWithEmptyView = (currentName: string) => {
|
||||
const pageNamesWithEmptyView: string[] = [
|
||||
SecurityPageName.hosts,
|
||||
SecurityPageName.network,
|
||||
SecurityPageName.timelines,
|
||||
SecurityPageName.overview,
|
||||
SecurityPageName.users,
|
||||
];
|
||||
return pageNamesWithEmptyView.includes(currentName);
|
||||
};
|
||||
|
||||
export const useShowPagesWithEmptyView = () => {
|
||||
const [{ pageName }] = useRouteSpy();
|
||||
const { indicesExist } = useSourcererDataView();
|
||||
|
||||
const shouldShowEmptyState = isPageNameWithEmptyView(pageName) && !indicesExist;
|
||||
|
||||
const [showEmptyState, setShowEmptyState] = useState(shouldShowEmptyState);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowEmptyState) {
|
||||
setShowEmptyState(true);
|
||||
} else {
|
||||
setShowEmptyState(false);
|
||||
}
|
||||
}, [shouldShowEmptyState]);
|
||||
|
||||
return showEmptyState;
|
||||
};
|
|
@ -26,11 +26,10 @@ import { Hosts } from './hosts';
|
|||
import { HostsTabs } from './hosts_tabs';
|
||||
import { useSourcererDataView } from '../../../common/containers/sourcerer';
|
||||
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
|
||||
import { LandingPageComponent } from '../../../common/components/landing_page';
|
||||
import { InputsModelId } from '../../../common/store/inputs/constants';
|
||||
|
||||
jest.mock('../../../common/containers/sourcerer');
|
||||
|
||||
jest.mock('../../../common/components/landing_page');
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
// For now let's forget about SiemSearchBar and QueryBar
|
||||
jest.mock('../../../common/components/search_bar', () => ({
|
||||
|
@ -107,7 +106,7 @@ describe('Hosts - rendering', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
|
||||
expect(wrapper.find('[data-test-subj="siem-landing-page"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it DOES NOT render the Setup Instructions text when an index is available', async () => {
|
||||
|
|
|
@ -109,6 +109,7 @@ jest.mock('../../../../common/components/search_bar', () => ({
|
|||
jest.mock('../../../../common/components/query_bar', () => ({
|
||||
QueryBar: () => null,
|
||||
}));
|
||||
jest.mock('../../../../common/components/landing_page');
|
||||
|
||||
const getMockHistory = (ip: string) => ({
|
||||
length: 2,
|
||||
|
@ -160,6 +161,10 @@ describe('Network Details', () => {
|
|||
|
||||
test('it renders', () => {
|
||||
const ip = '123.456.78.90';
|
||||
(useSourcererDataView as jest.Mock).mockReturnValue({
|
||||
indicesExist: true,
|
||||
indexPattern: {},
|
||||
});
|
||||
(useParams as jest.Mock).mockReturnValue({
|
||||
detailName: ip,
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
|
@ -197,4 +202,24 @@ describe('Network Details', () => {
|
|||
.text()
|
||||
).toEqual('fe80::24ce:f7ff:fede:a571');
|
||||
});
|
||||
|
||||
test('it renders landing page component when no indices exist', () => {
|
||||
const ip = '123.456.78.90';
|
||||
(useSourcererDataView as jest.Mock).mockReturnValue({
|
||||
indicesExist: false,
|
||||
indexPattern: {},
|
||||
});
|
||||
(useParams as jest.Mock).mockReturnValue({
|
||||
detailName: ip,
|
||||
flowTarget: FlowTargetSourceDest.source,
|
||||
});
|
||||
const wrapper = mount(
|
||||
<TestProviders store={store}>
|
||||
<Router history={getMockHistory(ip)}>
|
||||
<NetworkDetails />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="siem-landing-page"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,9 +26,10 @@ import { inputsActions } from '../../../common/store/inputs';
|
|||
import { Network } from './network';
|
||||
import { NetworkRoutes } from './navigation';
|
||||
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
|
||||
import { LandingPageComponent } from '../../../common/components/landing_page';
|
||||
|
||||
import { InputsModelId } from '../../../common/store/inputs/constants';
|
||||
|
||||
jest.mock('../../../common/components/landing_page');
|
||||
jest.mock('../../../common/containers/sourcerer');
|
||||
|
||||
// Test will fail because we will to need to mock some core services to make the test work
|
||||
|
@ -134,7 +135,7 @@ describe('Network page - rendering', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
|
||||
expect(wrapper.find(`[data-test-subj="siem-landing-page"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it DOES NOT render getting started page when an index is available', async () => {
|
||||
|
|
|
@ -15,8 +15,8 @@ import { TabNavigation } from '../../../common/components/navigation/tab_navigat
|
|||
import { Users } from './users';
|
||||
import { useSourcererDataView } from '../../../common/containers/sourcerer';
|
||||
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
|
||||
import { LandingPageComponent } from '../../../common/components/landing_page';
|
||||
|
||||
jest.mock('../../../common/components/landing_page');
|
||||
jest.mock('../../../common/containers/sourcerer');
|
||||
jest.mock('../../../common/components/search_bar', () => ({
|
||||
SiemSearchBar: () => null,
|
||||
|
@ -84,7 +84,7 @@ describe('Users - rendering', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
|
||||
expect(wrapper.find(`[data-test-subj="siem-landing-page"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it should render tab navigation', async () => {
|
||||
|
|
|
@ -15,6 +15,8 @@ import { DataQuality } from './data_quality';
|
|||
import { HOT, WARM, UNMANAGED } from './translations';
|
||||
|
||||
const mockedUseKibana = mockUseKibana();
|
||||
|
||||
jest.mock('../../common/components/landing_page');
|
||||
jest.mock('../../common/lib/kibana', () => {
|
||||
const original = jest.requireActual('../../common/lib/kibana');
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ jest.mock('../../common/components/filters_global', () => ({
|
|||
FiltersGlobal: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
jest.mock('../../common/components/landing_page');
|
||||
|
||||
const defaultUseSourcererReturn = {
|
||||
indicesExist: true,
|
||||
loading: false,
|
||||
|
|
|
@ -4,65 +4,27 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { LandingPage } from './landing';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { Router } from '@kbn/shared-ux-router';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { TestProviders } from '../../common/mock/test_providers';
|
||||
import React from 'react';
|
||||
|
||||
jest.mock('../../common/lib/kibana', () => ({
|
||||
useKibana: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../common/components/landing_page', () => ({
|
||||
LandingPageComponent: jest
|
||||
jest.mock('../../common/components/landing_page');
|
||||
jest.mock('../../common/components/page_wrapper', () => ({
|
||||
SecuritySolutionPageWrapper: jest
|
||||
.fn()
|
||||
.mockReturnValue(<div data-test-subj="default-get-started-page" />),
|
||||
.mockImplementation(({ children }) => <div>{children}</div>),
|
||||
}));
|
||||
|
||||
jest.mock('react-use/lib/useObservable', () => jest.fn((fn) => fn()));
|
||||
|
||||
const history = createBrowserHistory();
|
||||
describe('LandingPage', () => {
|
||||
const mockGetStartedComponent = jest.fn();
|
||||
const history = createBrowserHistory();
|
||||
const mockSecuritySolutionTemplateWrapper = jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => <div>{children}</div>);
|
||||
|
||||
const renderPage = () =>
|
||||
render(
|
||||
it('renders page properly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<Router history={history}>
|
||||
<LandingPage />
|
||||
</Router>,
|
||||
{ wrapper: TestProviders }
|
||||
</Router>
|
||||
);
|
||||
|
||||
beforeAll(() => {
|
||||
(useKibana as jest.Mock).mockReturnValue({
|
||||
services: {
|
||||
securityLayout: {
|
||||
getPluginWrapper: jest.fn().mockReturnValue(mockSecuritySolutionTemplateWrapper),
|
||||
},
|
||||
getStartedComponent$: mockGetStartedComponent,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('renders the default component', () => {
|
||||
const { queryByTestId } = renderPage();
|
||||
expect(queryByTestId('default-get-started-page')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the get started component', () => {
|
||||
mockGetStartedComponent.mockReturnValue(<div data-test-subj="get-started" />);
|
||||
const { queryByTestId } = renderPage();
|
||||
|
||||
expect(queryByTestId('default-get-started-page')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('get-started')).toBeInTheDocument();
|
||||
expect(queryByTestId('siem-landing-page')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,28 +6,28 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { Chat } from '@kbn/cloud-chat-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { SpyRoute } from '../../common/utils/route/spy_routes';
|
||||
import { SecurityPageName } from '../../../common/constants';
|
||||
import { LandingPageComponent } from '../../common/components/landing_page';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { PluginTemplateWrapper } from '../../common/components/plugin_template_wrapper';
|
||||
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
|
||||
|
||||
export const LandingPage = memo(() => {
|
||||
const { getStartedComponent$ } = useKibana().services;
|
||||
const GetStartedComponent = useObservable(getStartedComponent$);
|
||||
|
||||
return (
|
||||
<>
|
||||
{GetStartedComponent ?? (
|
||||
<PluginTemplateWrapper>
|
||||
<LandingPageComponent />
|
||||
</PluginTemplateWrapper>
|
||||
)}
|
||||
<Chat />
|
||||
<SpyRoute pageName={SecurityPageName.landing} />
|
||||
</>
|
||||
<div
|
||||
css={css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
`}
|
||||
>
|
||||
<SecuritySolutionPageWrapper noPadding>
|
||||
<LandingPageComponent />
|
||||
<Chat />
|
||||
<SpyRoute pageName={SecurityPageName.landing} />
|
||||
</SecuritySolutionPageWrapper>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ import { initialUserPrivilegesState } from '../../common/components/user_privile
|
|||
import type { EndpointPrivileges } from '../../../common/endpoint/types';
|
||||
import { useRiskScore } from '../../explore/containers/risk_score';
|
||||
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
|
||||
import { LandingPageComponent } from '../../common/components/landing_page';
|
||||
|
||||
const mockNavigateToApp = jest.fn();
|
||||
jest.mock('../../common/components/landing_page');
|
||||
jest.mock('../../common/lib/kibana', () => {
|
||||
const original = jest.requireActual('../../common/lib/kibana');
|
||||
|
||||
|
@ -308,7 +308,7 @@ describe('Overview', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(LandingPageComponent).exists()).toBe(true);
|
||||
expect(wrapper.find(`[data-test-subj="siem-landing-page"]`).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,13 +17,13 @@ import {
|
|||
} from '../../common/constants';
|
||||
import type { SecuritySubPluginRoutes } from '../app/types';
|
||||
|
||||
import { LandingPage } from './pages/landing';
|
||||
import { StatefulOverview } from './pages/overview';
|
||||
import { DataQuality } from './pages/data_quality';
|
||||
import { DetectionResponse } from './pages/detection_response';
|
||||
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
|
||||
import { EntityAnalyticsPage } from './pages/entity_analytics';
|
||||
import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper';
|
||||
import { LandingPage } from './pages/landing';
|
||||
|
||||
const OverviewRoutes = () => (
|
||||
<PluginTemplateWrapper>
|
||||
|
@ -42,9 +42,11 @@ const DetectionResponseRoutes = () => (
|
|||
);
|
||||
|
||||
const LandingRoutes = () => (
|
||||
<TrackApplicationView viewId={SecurityPageName.landing}>
|
||||
<LandingPage />
|
||||
</TrackApplicationView>
|
||||
<PluginTemplateWrapper>
|
||||
<TrackApplicationView viewId={SecurityPageName.landing}>
|
||||
<LandingPage />
|
||||
</TrackApplicationView>
|
||||
</PluginTemplateWrapper>
|
||||
);
|
||||
|
||||
const EntityAnalyticsRoutes = () => (
|
||||
|
|
|
@ -26,7 +26,6 @@ const FlyoutPaneComponent: React.FC<FlyoutPaneComponentProps> = ({
|
|||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const timeline = useMemo(
|
||||
() => (
|
||||
<StatefulTimeline
|
||||
|
|
|
@ -28,8 +28,8 @@ import { useSourcererDataView } from '../../../../common/containers/sourcerer';
|
|||
import { useNetworkDetails } from '../../../../explore/network/containers/details';
|
||||
import { networkModel } from '../../../../explore/network/store';
|
||||
import { useAnomaliesTableData } from '../../../../common/components/ml/anomaly/use_anomalies_table_data';
|
||||
import { LandingCards } from '../../../../common/components/landing_cards';
|
||||
import { useInstalledSecurityJobNameById } from '../../../../common/components/ml/hooks/use_installed_security_jobs';
|
||||
import { LandingPageComponent } from '../../../../common/components/landing_page';
|
||||
|
||||
interface ExpandableNetworkProps {
|
||||
expandedNetwork: { ip: string; flowTarget: FlowTargetSourceDest };
|
||||
|
@ -147,6 +147,6 @@ export const ExpandableNetworkDetails = ({
|
|||
jobNameById={jobNameById}
|
||||
/>
|
||||
) : (
|
||||
<LandingCards />
|
||||
<LandingPageComponent />
|
||||
);
|
||||
};
|
||||
|
|
|
@ -22,7 +22,21 @@ export const GetStartedComponent: React.FC = () => {
|
|||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<KibanaPageTemplate restrictWidth={false} contentBorder={false}>
|
||||
<KibanaPageTemplate
|
||||
restrictWidth={false}
|
||||
contentBorder={false}
|
||||
grow={true}
|
||||
/* this is the only page without padding in Security Solution,
|
||||
** ignoring main page wrapper padding using absolute positioning
|
||||
*/
|
||||
css={css`
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
`}
|
||||
>
|
||||
<KibanaPageTemplate.Header
|
||||
css={css`
|
||||
padding: 0 ${euiTheme.base * 2.25}px;
|
||||
|
|
|
@ -32861,18 +32861,6 @@
|
|||
"xpack.securitySolution.overview.ilmPhaseHot": "hot",
|
||||
"xpack.securitySolution.overview.ilmPhaseUnmanaged": "non géré",
|
||||
"xpack.securitySolution.overview.ilmPhaseWarm": "warm",
|
||||
"xpack.securitySolution.overview.landingCards.box.cloudCard.desc": "Évaluez votre niveau de cloud et protégez vos charges de travail contre les attaques.",
|
||||
"xpack.securitySolution.overview.landingCards.box.cloudCard.title": "Protection cloud de bout en bout",
|
||||
"xpack.securitySolution.overview.landingCards.box.endpoint.desc": "Prévention, collecte, détection et réponse, le tout avec Elastic Agent.",
|
||||
"xpack.securitySolution.overview.landingCards.box.endpoint.title": "Endpoint Security à grande échelle",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.cta": "Ajouter des intégrations de sécurité",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.desc": "Grâce à Elastic Security, les équipes sont en mesure de prévenir les menaces, de les détecter et de réagir en conséquence à l'échelle du cloud et grâce à sa vitesse. Ainsi, elles sécurisent les opérations de l'entreprise via une plateforme ouverte unifiée.",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.header": "Elastic Security",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.title": "La sécurité à la vitesse d'Elastic",
|
||||
"xpack.securitySolution.overview.landingCards.box.siemCard.desc": "Détection des menaces en constante évolution, investigations et réponses adaptées dans votre environnement.",
|
||||
"xpack.securitySolution.overview.landingCards.box.siemCard.title": "SIEM pour le centre opérationnel de sécurité moderne",
|
||||
"xpack.securitySolution.overview.landingCards.box.unify.desc": "Elastic Security modernise les opérations de sécurité en facilitant l'analyse des données collectées au fil des ans, en automatisant les principaux processus et en protégeant chaque hôte.",
|
||||
"xpack.securitySolution.overview.landingCards.box.unify.title": "Unification du SIEM, de la sécurité aux points de terminaison et de la sécurité cloud",
|
||||
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "En savoir plus",
|
||||
"xpack.securitySolution.overview.networkAction": "Afficher le réseau",
|
||||
"xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat",
|
||||
|
|
|
@ -32842,18 +32842,6 @@
|
|||
"xpack.securitySolution.overview.ilmPhaseHot": "ホット",
|
||||
"xpack.securitySolution.overview.ilmPhaseUnmanaged": "管理対象外",
|
||||
"xpack.securitySolution.overview.ilmPhaseWarm": "ウォーム",
|
||||
"xpack.securitySolution.overview.landingCards.box.cloudCard.desc": "クラウド態勢を評価し、ワークロードを攻撃から保護します。",
|
||||
"xpack.securitySolution.overview.landingCards.box.cloudCard.title": "エンドツーエンドのクラウド保護",
|
||||
"xpack.securitySolution.overview.landingCards.box.endpoint.desc": "防御から収集、検知、対応まで実行する、Elastic Agent。",
|
||||
"xpack.securitySolution.overview.landingCards.box.endpoint.title": "大規模なEndpoint Security",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.cta": "セキュリティ統合を追加",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.desc": "クラウドのスピードと規模を活かすElasticセキュリティで、脅威の防御、検知、対応まで完結させましょう。一元的でオープンなプラットフォーム1つで、事業経営を安全に維持できます。",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.header": "Elasticセキュリティ",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.title": "セキュリティも、Elasticの速さで",
|
||||
"xpack.securitySolution.overview.landingCards.box.siemCard.desc": "環境で進化する脅威を検知、調査、対応。",
|
||||
"xpack.securitySolution.overview.landingCards.box.siemCard.title": "最先端を行くSOCのSIEM",
|
||||
"xpack.securitySolution.overview.landingCards.box.unify.desc": "Elasticセキュリティは数年分に及ぶデータの分析を可能にするほか、主要プロセスを自動化し、全ホストを保護して、最先端のセキュリティ運用を実現します。",
|
||||
"xpack.securitySolution.overview.landingCards.box.unify.title": "SIEM、エンドポイントセキュリティ、クラウドセキュリティを一体化",
|
||||
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "詳細情報",
|
||||
"xpack.securitySolution.overview.networkAction": "ネットワークを表示",
|
||||
"xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat",
|
||||
|
|
|
@ -32838,18 +32838,6 @@
|
|||
"xpack.securitySolution.overview.ilmPhaseHot": "热",
|
||||
"xpack.securitySolution.overview.ilmPhaseUnmanaged": "未受管",
|
||||
"xpack.securitySolution.overview.ilmPhaseWarm": "温",
|
||||
"xpack.securitySolution.overview.landingCards.box.cloudCard.desc": "评估您的云态势并防止工作负载受到攻击。",
|
||||
"xpack.securitySolution.overview.landingCards.box.cloudCard.title": "端到端云防护",
|
||||
"xpack.securitySolution.overview.landingCards.box.endpoint.desc": "防御、收集、检测和响应 — 所有这些活动均可通过 Elastic 代理来实现。",
|
||||
"xpack.securitySolution.overview.landingCards.box.endpoint.title": "规模化终端安全",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.cta": "添加安全集成",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.desc": "借助 Elastic Security,团队能够以云技术所带来的规模和速度来防御、检测和应对各种威胁,在一体化的开放平台上保护业务运营的安全。",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.header": "Elastic Security",
|
||||
"xpack.securitySolution.overview.landingCards.box.siem.title": "以 Elastic 的速度实现安全保护",
|
||||
"xpack.securitySolution.overview.landingCards.box.siemCard.desc": "检测、调查并响应您环境中不断变化的威胁。",
|
||||
"xpack.securitySolution.overview.landingCards.box.siemCard.title": "适用于现代 SOC 的 SIEM",
|
||||
"xpack.securitySolution.overview.landingCards.box.unify.desc": "Elastic Security 实现了安全运营现代化,能够对多年的数据进行分析,自动执行关键流程,并保护每台主机。",
|
||||
"xpack.securitySolution.overview.landingCards.box.unify.title": "集 SIEM、Endpoint Security 和云安全于一体",
|
||||
"xpack.securitySolution.overview.linkPanelLearnMoreButton": "了解详情",
|
||||
"xpack.securitySolution.overview.networkAction": "查看网络",
|
||||
"xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue