mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Add Dashboards and Threat Hunting nav group pages (#130905)
This commit is contained in:
parent
3d3449c3e5
commit
6f399b375d
20 changed files with 393 additions and 1 deletions
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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 |
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
|
@ -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',
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
};
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
|
@ -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',
|
||||
}
|
||||
);
|
|
@ -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,
|
||||
},
|
||||
];
|
|
@ -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 };
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -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']>;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue