Add Dashboards and Threat Hunting nav group pages (#130905)

This commit is contained in:
Pablo Machado 2022-04-28 16:52:08 +02:00 committed by GitHub
parent 3d3449c3e5
commit 6f399b375d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 393 additions and 1 deletions

View file

@ -119,8 +119,12 @@ export enum SecurityPageName {
sessions = 'sessions',
usersEvents = 'users-events',
usersExternalAlerts = 'users-external_alerts',
threatHuntingLanding = 'threat-hunting',
dashboardsLanding = 'dashboards',
}
export const THREAT_HUNTING_PATH = '/threat_hunting' as const;
export const DASHBOARDS_PATH = '/dashboards' as const;
export const TIMELINES_PATH = '/timelines' as const;
export const CASES_PATH = '/cases' as const;
export const OVERVIEW_PATH = '/overview' as const;
@ -146,6 +150,8 @@ export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const;
export const APP_LANDING_PATH = `${APP_PATH}${LANDING_PATH}` as const;
export const APP_DETECTION_RESPONSE_PATH = `${APP_PATH}${DETECTION_RESPONSE_PATH}` as const;
export const APP_MANAGEMENT_PATH = `${APP_PATH}${MANAGEMENT_PATH}` as const;
export const APP_THREAT_HUNTING_PATH = `${APP_PATH}${THREAT_HUNTING_PATH}` as const;
export const APP_DASHBOARDS_PATH = `${APP_PATH}${DASHBOARDS_PATH}` as const;
export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}` as const;
export const APP_RULES_PATH = `${APP_PATH}${RULES_PATH}` as const;

View file

@ -33,6 +33,8 @@ import {
POLICIES,
ENDPOINTS,
GETTING_STARTED,
THREAT_HUNTING,
DASHBOARDS,
} from '../translations';
import {
OVERVIEW_PATH,
@ -54,6 +56,8 @@ import {
HOST_ISOLATION_EXCEPTIONS_PATH,
SERVER_APP_ID,
USERS_PATH,
THREAT_HUNTING_PATH,
DASHBOARDS_PATH,
} from '../../../common/constants';
import { ExperimentalFeatures } from '../../../common/experimental_features';
@ -105,6 +109,30 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
}),
],
},
{
id: SecurityPageName.threatHuntingLanding,
title: THREAT_HUNTING,
path: THREAT_HUNTING_PATH,
navLinkStatus: AppNavLinkStatus.hidden,
features: [FEATURE.general],
keywords: [
i18n.translate('xpack.securitySolution.search.threatHunting', {
defaultMessage: 'Threat Hunting',
}),
],
},
{
id: SecurityPageName.dashboardsLanding,
title: DASHBOARDS,
path: DASHBOARDS_PATH,
navLinkStatus: AppNavLinkStatus.hidden,
features: [FEATURE.general],
keywords: [
i18n.translate('xpack.securitySolution.search.dashboards', {
defaultMessage: 'Dashboards',
}),
],
},
{
id: SecurityPageName.detectionAndResponse,
title: DETECTION_RESPONSE,

View file

@ -26,6 +26,14 @@ export const GETTING_STARTED = i18n.translate('xpack.securitySolution.navigation
defaultMessage: 'Getting started',
});
export const THREAT_HUNTING = i18n.translate('xpack.securitySolution.navigation.threatHunting', {
defaultMessage: 'Threat Hunting',
});
export const DASHBOARDS = i18n.translate('xpack.securitySolution.navigation.dashboards', {
defaultMessage: 'Dashboards',
});
export const NETWORK = i18n.translate('xpack.securitySolution.navigation.network', {
defaultMessage: 'Network',
});

View file

@ -37,4 +37,6 @@ export type UrlStateType =
| 'network'
| 'overview'
| 'rules'
| 'timeline';
| 'timeline'
| 'threat_hunting'
| 'dashboards';

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -0,0 +1,59 @@
/*
* 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 { render } from '@testing-library/react';
import React from 'react';
import { SecurityPageName } from '../../app/types';
import { TestProviders } from '../../common/mock';
import { LandingLinksImages, NavItem } from './landing_links_images';
import { SCREENSHOT_IMAGE_ALT } from './translations';
const DEFAULT_NAV_ITEM: NavItem = {
id: SecurityPageName.overview,
label: 'TEST LABEL',
description: 'TEST DESCRIPTION',
image: 'TEST_IMAGE.png',
};
jest.mock('../../common/lib/kibana/kibana_react', () => {
return {
useKibana: jest.fn().mockReturnValue({
services: {
application: {
getUrlForApp: jest.fn(),
},
},
}),
};
});
describe('LandingLinksImages', () => {
test('renders', () => {
const label = 'test label';
const { queryByText } = render(
<TestProviders>
<LandingLinksImages items={[{ ...DEFAULT_NAV_ITEM, label }]} />
</TestProviders>
);
expect(queryByText(label)).toBeInTheDocument();
});
test('renders image', () => {
const image = 'test_image.jpeg';
const label = 'TEST_LABEL';
const { getByAltText } = render(
<TestProviders>
<LandingLinksImages items={[{ ...DEFAULT_NAV_ITEM, image, label }]} />
</TestProviders>
);
expect(getByAltText(SCREENSHOT_IMAGE_ALT(label))).toHaveAttribute('src', image);
});
});

View file

@ -0,0 +1,74 @@
/*
* 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, EuiFlexItem, EuiImage, EuiPanel, EuiText, EuiTitle } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
import { SecurityPageName } from '../../app/types';
import { withSecuritySolutionLink } from '../../common/components/links';
import * as i18n from './translations';
interface LandingLinksImagesProps {
items: NavItem[];
}
export interface NavItem {
id: SecurityPageName;
label: string;
image: string;
description: string;
path?: string;
}
const PrimatyEuiTitle = styled(EuiTitle)`
color: ${(props) => props.theme.eui.euiColorPrimary};
`;
const LandingLinksDescripton = styled(EuiText)`
padding-top: ${({ theme }) => theme.eui.paddingSizes.xs};
max-width: 550px;
`;
const Link = styled.a`
color: inherit;
`;
const StyledFlexItem = styled(EuiFlexItem)`
align-items: center;
`;
const SecuritySolutionLink = withSecuritySolutionLink(Link);
const Content = styled(EuiFlexItem)`
padding-left: ${({ theme }) => theme.eui.paddingSizes.s};
`;
export const LandingLinksImages: React.FC<LandingLinksImagesProps> = ({ items }) => (
<EuiFlexGroup direction="column">
{items.map(({ label, description, path, image, id }) => (
<EuiFlexItem key={id}>
<SecuritySolutionLink deepLinkId={id} path={path}>
{/* Empty onClick is to force hover style on `EuiPanel` */}
<EuiPanel hasBorder hasShadow={false} paddingSize="m" onClick={() => {}}>
<EuiFlexGroup>
<StyledFlexItem grow={false}>
<EuiImage size="l" alt={i18n.SCREENSHOT_IMAGE_ALT(label)} src={image} />
</StyledFlexItem>
<Content>
<PrimatyEuiTitle size="s">
<h2>{label}</h2>
</PrimatyEuiTitle>
<LandingLinksDescripton size="s" color="text">
{description}
</LandingLinksDescripton>
</Content>
</EuiFlexGroup>
</EuiPanel>
</SecuritySolutionLink>
</EuiFlexItem>
))}
</EuiFlexGroup>
);

View file

@ -0,0 +1,14 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const SCREENSHOT_IMAGE_ALT = (pageName: string) =>
i18n.translate('xpack.securitySolution.landing.threatHunting.pageImageAlt', {
values: { pageName },
defaultMessage: '{pageName} page screenshot',
});

View file

@ -0,0 +1,19 @@
/*
* 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 { SecuritySubPlugin } from '../app/types';
import { routes } from './routes';
export class LandingPages {
public setup() {}
public start(): SecuritySubPlugin {
return {
routes,
};
}
}

View 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/security_solution/public/landing_pages'],
coverageDirectory:
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/security_solution/public/landing_pages',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/plugins/security_solution/public/landing_pages/**/*.{ts,tsx}',
],
// See: https://github.com/elastic/kibana/issues/117255, the moduleNameMapper creates mocks to avoid memory leaks from kibana core.
moduleNameMapper: {
'core/server$': '<rootDir>/x-pack/plugins/security_solution/server/__mocks__/core.mock.ts',
'task_manager/server$':
'<rootDir>/x-pack/plugins/security_solution/server/__mocks__/task_manager.mock.ts',
'alerting/server$': '<rootDir>/x-pack/plugins/security_solution/server/__mocks__/alert.mock.ts',
'actions/server$': '<rootDir>/x-pack/plugins/security_solution/server/__mocks__/action.mock.ts',
},
};

View file

@ -0,0 +1,35 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { SecurityPageName } from '../../app/types';
import { HeaderPage } from '../../common/components/header_page';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { LandingLinksImages, NavItem } from '../components/landing_links_images';
import { DASHBOARDS_PAGE_TITLE } from './translations';
import overviewPageImg from '../../common/images/overview_page.png';
import { OVERVIEW } from '../../app/translations';
const items: NavItem[] = [
{
id: SecurityPageName.overview,
label: OVERVIEW,
description: i18n.translate('xpack.securitySolution.landing.dashboards.overviewDescription', {
defaultMessage: 'What is going in your secuity environment',
}),
image: overviewPageImg,
},
];
export const DashboardsLandingPage = () => (
<SecuritySolutionPageWrapper>
<HeaderPage title={DASHBOARDS_PAGE_TITLE} />
<LandingLinksImages items={items} />
<SpyRoute pageName={SecurityPageName.dashboardsLanding} />
</SecuritySolutionPageWrapper>
);

View file

@ -0,0 +1,55 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { SecurityPageName } from '../../app/types';
import { HeaderPage } from '../../common/components/header_page';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { LandingLinksImages, NavItem } from '../components/landing_links_images';
import { THREAT_HUNTING_PAGE_TITLE } from './translations';
import userPageImg from '../../common/images/users_page.png';
import hostsPageImg from '../../common/images/hosts_page.png';
import networkPageImg from '../../common/images/network_page.png';
import { HOSTS, NETWORK, USERS } from '../../app/translations';
const items: NavItem[] = [
{
id: SecurityPageName.hosts,
label: HOSTS,
description: i18n.translate('xpack.securitySolution.landing.threatHunting.hostsDescription', {
defaultMessage:
'Computer or other device that communicates with other hosts on a network. Hosts on a network include clients and servers -- that send or receive data, services or applications.',
}),
image: hostsPageImg,
},
{
id: SecurityPageName.network,
label: NETWORK,
description: i18n.translate('xpack.securitySolution.landing.threatHunting.networkDescription', {
defaultMessage:
'The action or process of interacting with others to exchange information and develop professional or social contacts.',
}),
image: networkPageImg,
},
{
id: SecurityPageName.users,
label: USERS,
description: i18n.translate('xpack.securitySolution.landing.threatHunting.usersDescription', {
defaultMessage: 'Sudo commands dashboard from the Logs System integration.',
}),
image: userPageImg,
},
];
export const ThreatHuntingLandingPage = () => (
<SecuritySolutionPageWrapper>
<HeaderPage title={THREAT_HUNTING_PAGE_TITLE} />
<LandingLinksImages items={items} />
<SpyRoute pageName={SecurityPageName.threatHuntingLanding} />
</SecuritySolutionPageWrapper>
);

View file

@ -0,0 +1,22 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const THREAT_HUNTING_PAGE_TITLE = i18n.translate(
'xpack.securitySolution.landing.threatHunting.pageTitle',
{
defaultMessage: 'Threat hunting',
}
);
export const DASHBOARDS_PAGE_TITLE = i18n.translate(
'xpack.securitySolution.landing.dashboards.pageTitle',
{
defaultMessage: 'Dashboards',
}
);

View file

@ -0,0 +1,37 @@
/*
* 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 { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types';
import { DASHBOARDS_PATH, THREAT_HUNTING_PATH } from '../../common/constants';
import { ThreatHuntingLandingPage } from './pages/threat_hunting';
import { DashboardsLandingPage } from './pages/dashboards';
export const ThreatHuntingRoutes = () => (
<TrackApplicationView viewId={SecurityPageName.threatHuntingLanding}>
<ThreatHuntingLandingPage />
</TrackApplicationView>
);
export const DashboardRoutes = () => (
<TrackApplicationView viewId={SecurityPageName.dashboardsLanding}>
<DashboardsLandingPage />
</TrackApplicationView>
);
export const routes: SecuritySubPluginRoutes = [
{
path: THREAT_HUNTING_PATH,
render: ThreatHuntingRoutes,
},
{
path: DASHBOARDS_PATH,
render: DashboardRoutes,
},
];

View file

@ -22,6 +22,7 @@ import { Rules } from './rules';
import { Timelines } from './timelines';
import { Management } from './management';
import { LandingPages } from './landing_pages';
/**
* The classes used to instantiate the sub plugins. These are grouped into a single object for the sake of bundling them in a single dynamic import.
@ -38,5 +39,6 @@ const subPluginClasses = {
Rules,
Timelines,
Management,
LandingPages,
};
export { subPluginClasses };

View file

@ -300,6 +300,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
overview: new subPluginClasses.Overview(),
timelines: new subPluginClasses.Timelines(),
management: new subPluginClasses.Management(),
landingPages: new subPluginClasses.LandingPages(),
};
}
return this._subPlugins;
@ -325,6 +326,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
network: subPlugins.network.start(storage),
timelines: subPlugins.timelines.start(),
management: subPlugins.management.start(core, plugins),
landingPages: subPlugins.landingPages.start(),
};
}
/**

View file

@ -44,6 +44,7 @@ import type { Overview } from './overview';
import type { Rules } from './rules';
import type { Timelines } from './timelines';
import type { Management } from './management';
import { LandingPages } from './landing_pages';
export interface SetupPlugins {
home?: HomePublicPluginSetup;
@ -106,6 +107,7 @@ export interface SubPlugins {
overview: Overview;
timelines: Timelines;
management: Management;
landingPages: LandingPages;
}
// TODO: find a better way to defined these types
@ -120,4 +122,5 @@ export interface StartedSubPlugins {
overview: ReturnType<Overview['start']>;
timelines: ReturnType<Timelines['start']>;
management: ReturnType<Management['start']>;
landingPages: ReturnType<LandingPages['start']>;
}