mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Nav unified (#131157)
This commit is contained in:
parent
9044d1f76f
commit
4ef0f1e0b3
17 changed files with 1251 additions and 23 deletions
|
@ -121,7 +121,6 @@ export enum SecurityPageName {
|
|||
usersExternalAlerts = 'users-external_alerts',
|
||||
threatHuntingLanding = 'threat-hunting',
|
||||
dashboardsLanding = 'dashboards',
|
||||
manageLanding = 'manage',
|
||||
}
|
||||
|
||||
export const THREAT_HUNTING_PATH = '/threat_hunting' as const;
|
||||
|
|
36
x-pack/plugins/security_solution/public/cases/links.ts
Normal file
36
x-pack/plugins/security_solution/public/cases/links.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { getCasesDeepLinks } from '@kbn/cases-plugin/public';
|
||||
import { CASES_PATH, SecurityPageName } from '../../common/constants';
|
||||
import { FEATURE, LinkItem } from '../common/links/types';
|
||||
|
||||
export const getCasesLinkItems = (): LinkItem => {
|
||||
const casesLinks = getCasesDeepLinks<LinkItem>({
|
||||
basePath: CASES_PATH,
|
||||
extend: {
|
||||
[SecurityPageName.case]: {
|
||||
globalNavEnabled: true,
|
||||
globalNavOrder: 9006,
|
||||
features: [FEATURE.casesRead],
|
||||
},
|
||||
[SecurityPageName.caseConfigure]: {
|
||||
features: [FEATURE.casesCrud],
|
||||
licenseType: 'gold',
|
||||
},
|
||||
[SecurityPageName.caseCreate]: {
|
||||
features: [FEATURE.casesCrud],
|
||||
},
|
||||
},
|
||||
});
|
||||
const { id, deepLinks, ...rest } = casesLinks;
|
||||
return {
|
||||
...rest,
|
||||
id: SecurityPageName.case,
|
||||
links: deepLinks as LinkItem[],
|
||||
};
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { UrlStateType } from '../url_state/constants';
|
||||
import type { SecurityPageName } from '../../../app/types';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { UrlState } from '../url_state/types';
|
||||
import { SiemRouteType } from '../../utils/route/types';
|
||||
|
||||
|
@ -40,26 +40,27 @@ export interface NavTab {
|
|||
pageId?: SecurityPageName;
|
||||
isBeta?: boolean;
|
||||
}
|
||||
|
||||
export type SecurityNavKey =
|
||||
| SecurityPageName.administration
|
||||
| SecurityPageName.alerts
|
||||
| SecurityPageName.blocklist
|
||||
| SecurityPageName.detectionAndResponse
|
||||
| SecurityPageName.case
|
||||
| SecurityPageName.endpoints
|
||||
| SecurityPageName.landing
|
||||
| SecurityPageName.policies
|
||||
| SecurityPageName.eventFilters
|
||||
| SecurityPageName.exceptions
|
||||
| SecurityPageName.hostIsolationExceptions
|
||||
| SecurityPageName.hosts
|
||||
| SecurityPageName.network
|
||||
| SecurityPageName.overview
|
||||
| SecurityPageName.rules
|
||||
| SecurityPageName.timelines
|
||||
| SecurityPageName.trustedApps
|
||||
| SecurityPageName.users;
|
||||
export const securityNavKeys = [
|
||||
SecurityPageName.administration,
|
||||
SecurityPageName.alerts,
|
||||
SecurityPageName.blocklist,
|
||||
SecurityPageName.detectionAndResponse,
|
||||
SecurityPageName.case,
|
||||
SecurityPageName.endpoints,
|
||||
SecurityPageName.landing,
|
||||
SecurityPageName.policies,
|
||||
SecurityPageName.eventFilters,
|
||||
SecurityPageName.exceptions,
|
||||
SecurityPageName.hostIsolationExceptions,
|
||||
SecurityPageName.hosts,
|
||||
SecurityPageName.network,
|
||||
SecurityPageName.overview,
|
||||
SecurityPageName.rules,
|
||||
SecurityPageName.timelines,
|
||||
SecurityPageName.trustedApps,
|
||||
SecurityPageName.users,
|
||||
] as const;
|
||||
export type SecurityNavKey = typeof securityNavKeys[number];
|
||||
|
||||
export type SecurityNav = Record<SecurityNavKey, NavTab>;
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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';
|
||||
import { SecurityPageName, THREAT_HUNTING_PATH } from '../../../common/constants';
|
||||
import { THREAT_HUNTING } from '../../app/translations';
|
||||
import { FEATURE, LinkItem, UserPermissions } from './types';
|
||||
import { links as hostsLinks } from '../../hosts/links';
|
||||
import { links as detectionLinks } from '../../detections/links';
|
||||
import { links as networkLinks } from '../../network/links';
|
||||
import { links as usersLinks } from '../../users/links';
|
||||
import { links as timelinesLinks } from '../../timelines/links';
|
||||
import { getCasesLinkItems } from '../../cases/links';
|
||||
import { links as managementLinks } from '../../management/links';
|
||||
import { gettingStartedLinks, dashboardsLandingLinks } from '../../overview/links';
|
||||
|
||||
export const appLinks: Readonly<LinkItem[]> = Object.freeze([
|
||||
gettingStartedLinks,
|
||||
dashboardsLandingLinks,
|
||||
detectionLinks,
|
||||
{
|
||||
id: SecurityPageName.threatHuntingLanding,
|
||||
title: THREAT_HUNTING,
|
||||
path: THREAT_HUNTING_PATH,
|
||||
globalNavEnabled: false,
|
||||
features: [FEATURE.general],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.threatHunting', {
|
||||
defaultMessage: 'Threat hunting',
|
||||
}),
|
||||
],
|
||||
links: [hostsLinks, networkLinks, usersLinks],
|
||||
},
|
||||
timelinesLinks,
|
||||
getCasesLinkItems(),
|
||||
managementLinks,
|
||||
]);
|
||||
|
||||
export const getAppLinks = async ({
|
||||
enableExperimental,
|
||||
license,
|
||||
capabilities,
|
||||
}: UserPermissions) => {
|
||||
// OLM team, implement async behavior here
|
||||
return appLinks;
|
||||
};
|
|
@ -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 * from './links';
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
* 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 {
|
||||
getAncestorLinksInfo,
|
||||
getDeepLinks,
|
||||
getInitialDeepLinks,
|
||||
getLinkInfo,
|
||||
getNavLinkItems,
|
||||
needsUrlState,
|
||||
} from './links';
|
||||
import { CASES_FEATURE_ID, SecurityPageName, SERVER_APP_ID } from '../../../common/constants';
|
||||
import { Capabilities } from '@kbn/core/types';
|
||||
import { AppDeepLink } from '@kbn/core/public';
|
||||
import { mockGlobalState } from '../mock';
|
||||
import { NavLinkItem } from './types';
|
||||
import { LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||
import { LicenseService } from '../../../common/license';
|
||||
|
||||
const mockExperimentalDefaults = mockGlobalState.app.enableExperimental;
|
||||
const mockCapabilities = {
|
||||
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: true },
|
||||
[SERVER_APP_ID]: { show: true },
|
||||
} as unknown as Capabilities;
|
||||
|
||||
const findDeepLink = (id: string, deepLinks: AppDeepLink[]): AppDeepLink | null =>
|
||||
deepLinks.reduce((deepLinkFound: AppDeepLink | null, deepLink) => {
|
||||
if (deepLinkFound !== null) {
|
||||
return deepLinkFound;
|
||||
}
|
||||
if (deepLink.id === id) {
|
||||
return deepLink;
|
||||
}
|
||||
if (deepLink.deepLinks) {
|
||||
return findDeepLink(id, deepLink.deepLinks);
|
||||
}
|
||||
return null;
|
||||
}, null);
|
||||
|
||||
const findNavLink = (id: SecurityPageName, navLinks: NavLinkItem[]): NavLinkItem | null =>
|
||||
navLinks.reduce((deepLinkFound: NavLinkItem | null, deepLink) => {
|
||||
if (deepLinkFound !== null) {
|
||||
return deepLinkFound;
|
||||
}
|
||||
if (deepLink.id === id) {
|
||||
return deepLink;
|
||||
}
|
||||
if (deepLink.links) {
|
||||
return findNavLink(id, deepLink.links);
|
||||
}
|
||||
return null;
|
||||
}, null);
|
||||
|
||||
// remove filter once new nav is live
|
||||
const allPages = Object.values(SecurityPageName).filter(
|
||||
(pageName) =>
|
||||
pageName !== SecurityPageName.explore &&
|
||||
pageName !== SecurityPageName.detections &&
|
||||
pageName !== SecurityPageName.investigate
|
||||
);
|
||||
const casesPages = [
|
||||
SecurityPageName.case,
|
||||
SecurityPageName.caseConfigure,
|
||||
SecurityPageName.caseCreate,
|
||||
];
|
||||
const featureFlagPages = [
|
||||
SecurityPageName.detectionAndResponse,
|
||||
SecurityPageName.hostsAuthentications,
|
||||
SecurityPageName.hostsRisk,
|
||||
SecurityPageName.usersRisk,
|
||||
];
|
||||
const premiumPages = [
|
||||
SecurityPageName.caseConfigure,
|
||||
SecurityPageName.hostsAnomalies,
|
||||
SecurityPageName.networkAnomalies,
|
||||
SecurityPageName.usersAnomalies,
|
||||
SecurityPageName.detectionAndResponse,
|
||||
SecurityPageName.hostsRisk,
|
||||
SecurityPageName.usersRisk,
|
||||
];
|
||||
const nonCasesPages = allPages.reduce(
|
||||
(acc: SecurityPageName[], p) =>
|
||||
casesPages.includes(p) || featureFlagPages.includes(p) ? acc : [p, ...acc],
|
||||
[]
|
||||
);
|
||||
|
||||
const licenseBasicMock = jest.fn().mockImplementation((arg: LicenseType) => arg === 'basic');
|
||||
const licensePremiumMock = jest.fn().mockReturnValue(true);
|
||||
const mockLicense = {
|
||||
isAtLeast: licensePremiumMock,
|
||||
} as unknown as LicenseService;
|
||||
|
||||
describe('security app link helpers', () => {
|
||||
beforeEach(() => {
|
||||
mockLicense.isAtLeast = licensePremiumMock;
|
||||
});
|
||||
describe('getInitialDeepLinks', () => {
|
||||
it('should return all pages in the app', () => {
|
||||
const links = getInitialDeepLinks();
|
||||
allPages.forEach((page) => expect(findDeepLink(page, links)).toBeTruthy());
|
||||
});
|
||||
});
|
||||
describe('getDeepLinks', () => {
|
||||
it('basicLicense should return only basic links', async () => {
|
||||
mockLicense.isAtLeast = licenseBasicMock;
|
||||
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findDeepLink(SecurityPageName.hostsAnomalies, links)).toBeFalsy();
|
||||
allPages.forEach((page) => {
|
||||
if (premiumPages.includes(page)) {
|
||||
return expect(findDeepLink(page, links)).toBeFalsy();
|
||||
}
|
||||
if (featureFlagPages.includes(page)) {
|
||||
// ignore feature flag pages
|
||||
return;
|
||||
}
|
||||
expect(findDeepLink(page, links)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
it('platinumLicense should return all links', async () => {
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
allPages.forEach((page) => {
|
||||
if (premiumPages.includes(page) && !featureFlagPages.includes(page)) {
|
||||
return expect(findDeepLink(page, links)).toBeTruthy();
|
||||
}
|
||||
if (featureFlagPages.includes(page)) {
|
||||
// ignore feature flag pages
|
||||
return;
|
||||
}
|
||||
expect(findDeepLink(page, links)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
it('hideWhenExperimentalKey hides entry when key = true', async () => {
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: { ...mockExperimentalDefaults, usersEnabled: true },
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findDeepLink(SecurityPageName.hostsAuthentications, links)).toBeFalsy();
|
||||
});
|
||||
it('hideWhenExperimentalKey shows entry when key = false', async () => {
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: { ...mockExperimentalDefaults, usersEnabled: false },
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findDeepLink(SecurityPageName.hostsAuthentications, links)).toBeTruthy();
|
||||
});
|
||||
it('experimentalKey shows entry when key = false', async () => {
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: {
|
||||
...mockExperimentalDefaults,
|
||||
riskyHostsEnabled: false,
|
||||
riskyUsersEnabled: false,
|
||||
detectionResponseEnabled: false,
|
||||
},
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findDeepLink(SecurityPageName.hostsRisk, links)).toBeFalsy();
|
||||
expect(findDeepLink(SecurityPageName.usersRisk, links)).toBeFalsy();
|
||||
expect(findDeepLink(SecurityPageName.detectionAndResponse, links)).toBeFalsy();
|
||||
});
|
||||
it('experimentalKey shows entry when key = true', async () => {
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: {
|
||||
...mockExperimentalDefaults,
|
||||
riskyHostsEnabled: true,
|
||||
riskyUsersEnabled: true,
|
||||
detectionResponseEnabled: true,
|
||||
},
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findDeepLink(SecurityPageName.hostsRisk, links)).toBeTruthy();
|
||||
expect(findDeepLink(SecurityPageName.usersRisk, links)).toBeTruthy();
|
||||
expect(findDeepLink(SecurityPageName.detectionAndResponse, links)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Removes siem features when siem capabilities are false', async () => {
|
||||
const capabilities = {
|
||||
...mockCapabilities,
|
||||
[SERVER_APP_ID]: { show: false },
|
||||
} as unknown as Capabilities;
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities,
|
||||
});
|
||||
nonCasesPages.forEach((page) => {
|
||||
// investigate is active for both Cases and Timelines pages
|
||||
if (page === SecurityPageName.investigate) {
|
||||
return expect(findDeepLink(page, links)).toBeTruthy();
|
||||
}
|
||||
return expect(findDeepLink(page, links)).toBeFalsy();
|
||||
});
|
||||
casesPages.forEach((page) => expect(findDeepLink(page, links)).toBeTruthy());
|
||||
});
|
||||
it('Removes cases features when cases capabilities are false', async () => {
|
||||
const capabilities = {
|
||||
...mockCapabilities,
|
||||
[CASES_FEATURE_ID]: { read_cases: false, crud_cases: false },
|
||||
} as unknown as Capabilities;
|
||||
const links = await getDeepLinks({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities,
|
||||
});
|
||||
nonCasesPages.forEach((page) => expect(findDeepLink(page, links)).toBeTruthy());
|
||||
casesPages.forEach((page) => expect(findDeepLink(page, links)).toBeFalsy());
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNavLinkItems', () => {
|
||||
it('basicLicense should return only basic links', () => {
|
||||
mockLicense.isAtLeast = licenseBasicMock;
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findNavLink(SecurityPageName.hostsAnomalies, links)).toBeFalsy();
|
||||
allPages.forEach((page) => {
|
||||
if (premiumPages.includes(page)) {
|
||||
return expect(findNavLink(page, links)).toBeFalsy();
|
||||
}
|
||||
if (featureFlagPages.includes(page)) {
|
||||
// ignore feature flag pages
|
||||
return;
|
||||
}
|
||||
expect(findNavLink(page, links)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
it('platinumLicense should return all links', () => {
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
allPages.forEach((page) => {
|
||||
if (premiumPages.includes(page) && !featureFlagPages.includes(page)) {
|
||||
return expect(findNavLink(page, links)).toBeTruthy();
|
||||
}
|
||||
if (featureFlagPages.includes(page)) {
|
||||
// ignore feature flag pages
|
||||
return;
|
||||
}
|
||||
expect(findNavLink(page, links)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
it('hideWhenExperimentalKey hides entry when key = true', () => {
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: { ...mockExperimentalDefaults, usersEnabled: true },
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findNavLink(SecurityPageName.hostsAuthentications, links)).toBeFalsy();
|
||||
});
|
||||
it('hideWhenExperimentalKey shows entry when key = false', () => {
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: { ...mockExperimentalDefaults, usersEnabled: false },
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findNavLink(SecurityPageName.hostsAuthentications, links)).toBeTruthy();
|
||||
});
|
||||
it('experimentalKey shows entry when key = false', () => {
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: {
|
||||
...mockExperimentalDefaults,
|
||||
riskyHostsEnabled: false,
|
||||
riskyUsersEnabled: false,
|
||||
detectionResponseEnabled: false,
|
||||
},
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findNavLink(SecurityPageName.hostsRisk, links)).toBeFalsy();
|
||||
expect(findNavLink(SecurityPageName.usersRisk, links)).toBeFalsy();
|
||||
expect(findNavLink(SecurityPageName.detectionAndResponse, links)).toBeFalsy();
|
||||
});
|
||||
it('experimentalKey shows entry when key = true', () => {
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: {
|
||||
...mockExperimentalDefaults,
|
||||
riskyHostsEnabled: true,
|
||||
riskyUsersEnabled: true,
|
||||
detectionResponseEnabled: true,
|
||||
},
|
||||
license: mockLicense,
|
||||
capabilities: mockCapabilities,
|
||||
});
|
||||
expect(findNavLink(SecurityPageName.hostsRisk, links)).toBeTruthy();
|
||||
expect(findNavLink(SecurityPageName.usersRisk, links)).toBeTruthy();
|
||||
expect(findNavLink(SecurityPageName.detectionAndResponse, links)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Removes siem features when siem capabilities are false', () => {
|
||||
const capabilities = {
|
||||
...mockCapabilities,
|
||||
[SERVER_APP_ID]: { show: false },
|
||||
} as unknown as Capabilities;
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities,
|
||||
});
|
||||
nonCasesPages.forEach((page) => {
|
||||
// investigate is active for both Cases and Timelines pages
|
||||
if (page === SecurityPageName.investigate) {
|
||||
return expect(findNavLink(page, links)).toBeTruthy();
|
||||
}
|
||||
return expect(findNavLink(page, links)).toBeFalsy();
|
||||
});
|
||||
casesPages.forEach((page) => expect(findNavLink(page, links)).toBeTruthy());
|
||||
});
|
||||
it('Removes cases features when cases capabilities are false', () => {
|
||||
const capabilities = {
|
||||
...mockCapabilities,
|
||||
[CASES_FEATURE_ID]: { read_cases: false, crud_cases: false },
|
||||
} as unknown as Capabilities;
|
||||
const links = getNavLinkItems({
|
||||
enableExperimental: mockExperimentalDefaults,
|
||||
license: mockLicense,
|
||||
capabilities,
|
||||
});
|
||||
nonCasesPages.forEach((page) => expect(findNavLink(page, links)).toBeTruthy());
|
||||
casesPages.forEach((page) => expect(findNavLink(page, links)).toBeFalsy());
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAncestorLinksInfo', () => {
|
||||
it('finds flattened links for hosts', () => {
|
||||
const hierarchy = getAncestorLinksInfo(SecurityPageName.hosts);
|
||||
expect(hierarchy).toEqual([
|
||||
{
|
||||
features: ['siem.show'],
|
||||
globalNavEnabled: false,
|
||||
globalSearchKeywords: ['Threat hunting'],
|
||||
id: 'threat-hunting',
|
||||
path: '/threat_hunting',
|
||||
title: 'Threat Hunting',
|
||||
},
|
||||
{
|
||||
globalNavEnabled: true,
|
||||
globalNavOrder: 9002,
|
||||
globalSearchEnabled: true,
|
||||
globalSearchKeywords: ['Hosts'],
|
||||
id: 'hosts',
|
||||
path: '/hosts',
|
||||
title: 'Hosts',
|
||||
},
|
||||
]);
|
||||
});
|
||||
it('finds flattened links for uncommonProcesses', () => {
|
||||
const hierarchy = getAncestorLinksInfo(SecurityPageName.uncommonProcesses);
|
||||
expect(hierarchy).toEqual([
|
||||
{
|
||||
features: ['siem.show'],
|
||||
globalNavEnabled: false,
|
||||
globalSearchKeywords: ['Threat hunting'],
|
||||
id: 'threat-hunting',
|
||||
path: '/threat_hunting',
|
||||
title: 'Threat Hunting',
|
||||
},
|
||||
{
|
||||
globalNavEnabled: true,
|
||||
globalNavOrder: 9002,
|
||||
globalSearchEnabled: true,
|
||||
globalSearchKeywords: ['Hosts'],
|
||||
id: 'hosts',
|
||||
path: '/hosts',
|
||||
title: 'Hosts',
|
||||
},
|
||||
{
|
||||
id: 'uncommon_processes',
|
||||
path: '/hosts/uncommonProcesses',
|
||||
title: 'Uncommon Processes',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('needsUrlState', () => {
|
||||
it('returns true when url state exists for page', () => {
|
||||
const needsUrl = needsUrlState(SecurityPageName.hosts);
|
||||
expect(needsUrl).toEqual(true);
|
||||
});
|
||||
it('returns false when url state does not exist for page', () => {
|
||||
const needsUrl = needsUrlState(SecurityPageName.landing);
|
||||
expect(needsUrl).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLinkInfo', () => {
|
||||
it('gets information for an individual link', () => {
|
||||
const linkInfo = getLinkInfo(SecurityPageName.hosts);
|
||||
expect(linkInfo).toEqual({
|
||||
globalNavEnabled: true,
|
||||
globalNavOrder: 9002,
|
||||
globalSearchEnabled: true,
|
||||
globalSearchKeywords: ['Hosts'],
|
||||
id: 'hosts',
|
||||
path: '/hosts',
|
||||
title: 'Hosts',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
197
x-pack/plugins/security_solution/public/common/links/links.ts
Normal file
197
x-pack/plugins/security_solution/public/common/links/links.ts
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* 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 { AppDeepLink, AppNavLinkStatus, Capabilities } from '@kbn/core/public';
|
||||
import { get } from 'lodash';
|
||||
import { SecurityPageName } from '../../../common/constants';
|
||||
import { appLinks, getAppLinks } from './app_links';
|
||||
import {
|
||||
Feature,
|
||||
LinkInfo,
|
||||
LinkItem,
|
||||
NavLinkItem,
|
||||
NormalizedLink,
|
||||
NormalizedLinks,
|
||||
UserPermissions,
|
||||
} from './types';
|
||||
|
||||
const createDeepLink = (link: LinkItem, linkProps?: UserPermissions): AppDeepLink => ({
|
||||
id: link.id,
|
||||
path: link.path,
|
||||
title: link.title,
|
||||
...(link.links && link.links.length
|
||||
? {
|
||||
deepLinks: reduceLinks<AppDeepLink>({
|
||||
links: link.links,
|
||||
linkProps,
|
||||
formatFunction: createDeepLink,
|
||||
}),
|
||||
}
|
||||
: {}),
|
||||
...(link.icon != null ? { euiIconType: link.icon } : {}),
|
||||
...(link.image != null ? { icon: link.image } : {}),
|
||||
...(link.globalSearchKeywords != null ? { keywords: link.globalSearchKeywords } : {}),
|
||||
...(link.globalNavEnabled != null
|
||||
? { navLinkStatus: link.globalNavEnabled ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden }
|
||||
: {}),
|
||||
...(link.globalNavOrder != null ? { order: link.globalNavOrder } : {}),
|
||||
...(link.globalSearchEnabled != null ? { searchable: link.globalSearchEnabled } : {}),
|
||||
});
|
||||
|
||||
const createNavLinkItem = (link: LinkItem, linkProps?: UserPermissions): NavLinkItem => ({
|
||||
id: link.id,
|
||||
path: link.path,
|
||||
title: link.title,
|
||||
...(link.description != null ? { description: link.description } : {}),
|
||||
...(link.icon != null ? { icon: link.icon } : {}),
|
||||
...(link.image != null ? { image: link.image } : {}),
|
||||
...(link.links && link.links.length
|
||||
? {
|
||||
links: reduceLinks<NavLinkItem>({
|
||||
links: link.links,
|
||||
linkProps,
|
||||
formatFunction: createNavLinkItem,
|
||||
}),
|
||||
}
|
||||
: {}),
|
||||
...(link.skipUrlState != null ? { skipUrlState: link.skipUrlState } : {}),
|
||||
});
|
||||
|
||||
const hasFeaturesCapability = (
|
||||
features: Feature[] | undefined,
|
||||
capabilities: Capabilities
|
||||
): boolean => {
|
||||
if (!features) {
|
||||
return true;
|
||||
}
|
||||
return features.some((featureKey) => get(capabilities, featureKey, false));
|
||||
};
|
||||
|
||||
const isLinkAllowed = (link: LinkItem, linkProps?: UserPermissions) =>
|
||||
!(
|
||||
linkProps != null &&
|
||||
// exclude link when license is basic and link is premium
|
||||
((linkProps.license && !linkProps.license.isAtLeast(link.licenseType ?? 'basic')) ||
|
||||
// exclude link when enableExperimental[hideWhenExperimentalKey] is enabled and link has hideWhenExperimentalKey
|
||||
(link.hideWhenExperimentalKey != null &&
|
||||
linkProps.enableExperimental[link.hideWhenExperimentalKey]) ||
|
||||
// exclude link when enableExperimental[experimentalKey] is disabled and link has experimentalKey
|
||||
(link.experimentalKey != null && !linkProps.enableExperimental[link.experimentalKey]) ||
|
||||
// exclude link when link is not part of enabled feature capabilities
|
||||
(linkProps.capabilities != null &&
|
||||
!hasFeaturesCapability(link.features, linkProps.capabilities)))
|
||||
);
|
||||
|
||||
export function reduceLinks<T>({
|
||||
links,
|
||||
linkProps,
|
||||
formatFunction,
|
||||
}: {
|
||||
links: Readonly<LinkItem[]>;
|
||||
linkProps?: UserPermissions;
|
||||
formatFunction: (link: LinkItem, linkProps?: UserPermissions) => T;
|
||||
}): T[] {
|
||||
return links.reduce(
|
||||
(deepLinks: T[], link: LinkItem) =>
|
||||
isLinkAllowed(link, linkProps) ? [...deepLinks, formatFunction(link, linkProps)] : deepLinks,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
export const getInitialDeepLinks = (): AppDeepLink[] => {
|
||||
return appLinks.map((link) => createDeepLink(link));
|
||||
};
|
||||
|
||||
export const getDeepLinks = async ({
|
||||
enableExperimental,
|
||||
license,
|
||||
capabilities,
|
||||
}: UserPermissions): Promise<AppDeepLink[]> => {
|
||||
const links = await getAppLinks({ enableExperimental, license, capabilities });
|
||||
return reduceLinks<AppDeepLink>({
|
||||
links,
|
||||
linkProps: { enableExperimental, license, capabilities },
|
||||
formatFunction: createDeepLink,
|
||||
});
|
||||
};
|
||||
|
||||
export const getNavLinkItems = ({
|
||||
enableExperimental,
|
||||
license,
|
||||
capabilities,
|
||||
}: UserPermissions): NavLinkItem[] =>
|
||||
reduceLinks<NavLinkItem>({
|
||||
links: appLinks,
|
||||
linkProps: { enableExperimental, license, capabilities },
|
||||
formatFunction: createNavLinkItem,
|
||||
});
|
||||
|
||||
/**
|
||||
* Recursive function to create the `NormalizedLinks` structure from a `LinkItem` array parameter
|
||||
*/
|
||||
const getNormalizedLinks = (
|
||||
currentLinks: Readonly<LinkItem[]>,
|
||||
parentId?: SecurityPageName
|
||||
): NormalizedLinks => {
|
||||
const result = currentLinks.reduce<Partial<NormalizedLinks>>(
|
||||
(normalized, { links, ...currentLink }) => {
|
||||
normalized[currentLink.id] = {
|
||||
...currentLink,
|
||||
parentId,
|
||||
};
|
||||
if (links && links.length > 0) {
|
||||
Object.assign(normalized, getNormalizedLinks(links, currentLink.id));
|
||||
}
|
||||
return normalized;
|
||||
},
|
||||
{}
|
||||
);
|
||||
return result as NormalizedLinks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalized indexed version of the global `links` array, referencing the parent by id, instead of having nested links children
|
||||
*/
|
||||
const normalizedLinks: Readonly<NormalizedLinks> = Object.freeze(getNormalizedLinks(appLinks));
|
||||
|
||||
/**
|
||||
* Returns the `NormalizedLink` from a link id parameter.
|
||||
* The object reference is frozen to make sure it is not mutated by the caller.
|
||||
*/
|
||||
const getNormalizedLink = (id: SecurityPageName): Readonly<NormalizedLink> =>
|
||||
Object.freeze(normalizedLinks[id]);
|
||||
|
||||
/**
|
||||
* Returns the `LinkInfo` from a link id parameter
|
||||
*/
|
||||
export const getLinkInfo = (id: SecurityPageName): LinkInfo => {
|
||||
// discards the parentId and creates the linkInfo copy.
|
||||
const { parentId, ...linkInfo } = getNormalizedLink(id);
|
||||
return linkInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the `LinkInfo` of all the ancestors to the parameter id link, also included.
|
||||
*/
|
||||
export const getAncestorLinksInfo = (id: SecurityPageName): LinkInfo[] => {
|
||||
const ancestors: LinkInfo[] = [];
|
||||
let currentId: SecurityPageName | undefined = id;
|
||||
while (currentId) {
|
||||
const { parentId, ...linkInfo } = getNormalizedLink(currentId);
|
||||
ancestors.push(linkInfo);
|
||||
currentId = parentId;
|
||||
}
|
||||
return ancestors.reverse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns `true` if the links needs to carry the application state in the url.
|
||||
* Defaults to `true` if the `skipUrlState` property of the `LinkItem` is `undefined`.
|
||||
*/
|
||||
export const needsUrlState = (id: SecurityPageName): boolean => {
|
||||
return !getNormalizedLink(id).skipUrlState;
|
||||
};
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { Capabilities } from '@kbn/core/types';
|
||||
import { LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||
import { LicenseService } from '../../../common/license';
|
||||
import { ExperimentalFeatures } from '../../../common/experimental_features';
|
||||
import { CASES_FEATURE_ID, SecurityPageName, SERVER_APP_ID } from '../../../common/constants';
|
||||
|
||||
export const FEATURE = {
|
||||
general: `${SERVER_APP_ID}.show`,
|
||||
casesRead: `${CASES_FEATURE_ID}.read_cases`,
|
||||
casesCrud: `${CASES_FEATURE_ID}.crud_cases`,
|
||||
};
|
||||
|
||||
export type Feature = Readonly<typeof FEATURE[keyof typeof FEATURE]>;
|
||||
|
||||
export interface UserPermissions {
|
||||
enableExperimental: ExperimentalFeatures;
|
||||
license?: LicenseService;
|
||||
capabilities?: Capabilities;
|
||||
}
|
||||
|
||||
export interface LinkItem {
|
||||
description?: string;
|
||||
disabled?: boolean; // default false
|
||||
/**
|
||||
* Displays deep link when feature flag is enabled.
|
||||
*/
|
||||
experimentalKey?: keyof ExperimentalFeatures;
|
||||
features?: Feature[];
|
||||
/**
|
||||
* Hides deep link when feature flag is enabled.
|
||||
*/
|
||||
globalNavEnabled?: boolean; // default false
|
||||
globalNavOrder?: number;
|
||||
globalSearchEnabled?: boolean;
|
||||
globalSearchKeywords?: string[];
|
||||
hideWhenExperimentalKey?: keyof ExperimentalFeatures;
|
||||
icon?: string;
|
||||
id: SecurityPageName;
|
||||
image?: string;
|
||||
isBeta?: boolean;
|
||||
licenseType?: LicenseType;
|
||||
links?: LinkItem[];
|
||||
path: string;
|
||||
skipUrlState?: boolean; // defaults to false
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface NavLinkItem {
|
||||
description?: string;
|
||||
icon?: string;
|
||||
id: SecurityPageName;
|
||||
links?: NavLinkItem[];
|
||||
image?: string;
|
||||
path: string;
|
||||
title: string;
|
||||
skipUrlState?: boolean; // default to false
|
||||
}
|
||||
|
||||
export type LinkInfo = Omit<LinkItem, 'links'>;
|
||||
export type NormalizedLink = LinkInfo & { parentId?: SecurityPageName };
|
||||
export type NormalizedLinks = Record<SecurityPageName, NormalizedLink>;
|
25
x-pack/plugins/security_solution/public/detections/links.ts
Normal file
25
x-pack/plugins/security_solution/public/detections/links.ts
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.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALERTS_PATH, SecurityPageName } from '../../common/constants';
|
||||
import { ALERTS } from '../app/translations';
|
||||
import { LinkItem, FEATURE } from '../common/links/types';
|
||||
|
||||
export const links: LinkItem = {
|
||||
id: SecurityPageName.alerts,
|
||||
title: ALERTS,
|
||||
path: ALERTS_PATH,
|
||||
features: [FEATURE.general],
|
||||
globalNavEnabled: true,
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
],
|
||||
globalSearchEnabled: true,
|
||||
globalNavOrder: 9001,
|
||||
};
|
79
x-pack/plugins/security_solution/public/hosts/links.ts
Normal file
79
x-pack/plugins/security_solution/public/hosts/links.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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';
|
||||
import { HOSTS_PATH, SecurityPageName } from '../../common/constants';
|
||||
import { HOSTS } from '../app/translations';
|
||||
import { LinkItem } from '../common/links/types';
|
||||
|
||||
export const links: LinkItem = {
|
||||
id: SecurityPageName.hosts,
|
||||
title: HOSTS,
|
||||
path: HOSTS_PATH,
|
||||
globalNavEnabled: true,
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.hosts', {
|
||||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
],
|
||||
globalSearchEnabled: true,
|
||||
globalNavOrder: 9002,
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.hostsAuthentications,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.authentications', {
|
||||
defaultMessage: 'Authentications',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/authentications`,
|
||||
hideWhenExperimentalKey: 'usersEnabled',
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.uncommonProcesses,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.uncommonProcesses', {
|
||||
defaultMessage: 'Uncommon Processes',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/uncommonProcesses`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostsAnomalies,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/anomalies`,
|
||||
licenseType: 'gold',
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostsEvents,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/events`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostsExternalAlerts,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.externalAlerts', {
|
||||
defaultMessage: 'External Alerts',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/externalAlerts`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostsRisk,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.risk', {
|
||||
defaultMessage: 'Hosts by risk',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/hostRisk`,
|
||||
experimentalKey: 'riskyHostsEnabled',
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.sessions,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.sessions', {
|
||||
defaultMessage: 'Sessions',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/sessions`,
|
||||
isBeta: true,
|
||||
},
|
||||
],
|
||||
};
|
|
@ -27,7 +27,7 @@ export const DashboardRoutes = () => (
|
|||
);
|
||||
|
||||
export const ManageRoutes = () => (
|
||||
<TrackApplicationView viewId={SecurityPageName.manageLanding}>
|
||||
<TrackApplicationView viewId={SecurityPageName.administration}>
|
||||
<ManageLandingPage />
|
||||
</TrackApplicationView>
|
||||
);
|
||||
|
|
111
x-pack/plugins/security_solution/public/management/links.ts
Normal file
111
x-pack/plugins/security_solution/public/management/links.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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';
|
||||
import {
|
||||
BLOCKLIST_PATH,
|
||||
ENDPOINTS_PATH,
|
||||
EVENT_FILTERS_PATH,
|
||||
EXCEPTIONS_PATH,
|
||||
HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
MANAGEMENT_PATH,
|
||||
POLICIES_PATH,
|
||||
RULES_PATH,
|
||||
SecurityPageName,
|
||||
TRUSTED_APPS_PATH,
|
||||
} from '../../common/constants';
|
||||
import {
|
||||
BLOCKLIST,
|
||||
ENDPOINTS,
|
||||
EVENT_FILTERS,
|
||||
EXCEPTIONS,
|
||||
HOST_ISOLATION_EXCEPTIONS,
|
||||
MANAGE,
|
||||
POLICIES,
|
||||
RULES,
|
||||
TRUSTED_APPLICATIONS,
|
||||
} from '../app/translations';
|
||||
import { FEATURE, LinkItem } from '../common/links/types';
|
||||
|
||||
export const links: LinkItem = {
|
||||
id: SecurityPageName.administration,
|
||||
title: MANAGE,
|
||||
path: MANAGEMENT_PATH,
|
||||
skipUrlState: true,
|
||||
globalNavEnabled: false,
|
||||
features: [FEATURE.general],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.manage', {
|
||||
defaultMessage: 'Manage',
|
||||
}),
|
||||
],
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.rules,
|
||||
title: RULES,
|
||||
path: RULES_PATH,
|
||||
globalNavEnabled: false,
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.rules', {
|
||||
defaultMessage: 'Rules',
|
||||
}),
|
||||
],
|
||||
globalSearchEnabled: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.exceptions,
|
||||
title: EXCEPTIONS,
|
||||
path: EXCEPTIONS_PATH,
|
||||
globalNavEnabled: false,
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.exceptions', {
|
||||
defaultMessage: 'Exception lists',
|
||||
}),
|
||||
],
|
||||
globalSearchEnabled: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.endpoints,
|
||||
globalNavEnabled: true,
|
||||
title: ENDPOINTS,
|
||||
globalNavOrder: 9006,
|
||||
path: ENDPOINTS_PATH,
|
||||
skipUrlState: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.policies,
|
||||
title: POLICIES,
|
||||
path: POLICIES_PATH,
|
||||
skipUrlState: true,
|
||||
experimentalKey: 'policyListEnabled',
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.trustedApps,
|
||||
title: TRUSTED_APPLICATIONS,
|
||||
path: TRUSTED_APPS_PATH,
|
||||
skipUrlState: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.eventFilters,
|
||||
title: EVENT_FILTERS,
|
||||
path: EVENT_FILTERS_PATH,
|
||||
skipUrlState: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostIsolationExceptions,
|
||||
title: HOST_ISOLATION_EXCEPTIONS,
|
||||
path: HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
skipUrlState: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.blocklist,
|
||||
title: BLOCKLIST,
|
||||
path: BLOCKLIST_PATH,
|
||||
skipUrlState: true,
|
||||
},
|
||||
],
|
||||
};
|
62
x-pack/plugins/security_solution/public/network/links.ts
Normal file
62
x-pack/plugins/security_solution/public/network/links.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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';
|
||||
import { NETWORK_PATH, SecurityPageName } from '../../common/constants';
|
||||
import { NETWORK } from '../app/translations';
|
||||
import { LinkItem } from '../common/links/types';
|
||||
|
||||
export const links: LinkItem = {
|
||||
id: SecurityPageName.network,
|
||||
title: NETWORK,
|
||||
path: NETWORK_PATH,
|
||||
globalNavEnabled: true,
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.network', {
|
||||
defaultMessage: 'Network',
|
||||
}),
|
||||
],
|
||||
globalNavOrder: 9003,
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.networkDns,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.network.dns', {
|
||||
defaultMessage: 'DNS',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/dns`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkHttp,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.network.http', {
|
||||
defaultMessage: 'HTTP',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/http`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkTls,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.network.tls', {
|
||||
defaultMessage: 'TLS',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/tls`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkExternalAlerts,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.network.externalAlerts', {
|
||||
defaultMessage: 'External Alerts',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/external-alerts`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkAnomalies,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.hosts.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/anomalies`,
|
||||
licenseType: 'gold',
|
||||
},
|
||||
],
|
||||
};
|
73
x-pack/plugins/security_solution/public/overview/links.ts
Normal file
73
x-pack/plugins/security_solution/public/overview/links.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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';
|
||||
import {
|
||||
DASHBOARDS_PATH,
|
||||
DETECTION_RESPONSE_PATH,
|
||||
LANDING_PATH,
|
||||
OVERVIEW_PATH,
|
||||
SecurityPageName,
|
||||
} from '../../common/constants';
|
||||
import { DASHBOARDS, DETECTION_RESPONSE, GETTING_STARTED, OVERVIEW } from '../app/translations';
|
||||
import { FEATURE, LinkItem } from '../common/links/types';
|
||||
|
||||
export const overviewLinks: LinkItem = {
|
||||
id: SecurityPageName.overview,
|
||||
title: OVERVIEW,
|
||||
path: OVERVIEW_PATH,
|
||||
globalNavEnabled: true,
|
||||
features: [FEATURE.general],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.overview', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
],
|
||||
globalNavOrder: 9000,
|
||||
};
|
||||
|
||||
export const gettingStartedLinks: LinkItem = {
|
||||
id: SecurityPageName.landing,
|
||||
title: GETTING_STARTED,
|
||||
path: LANDING_PATH,
|
||||
globalNavEnabled: false,
|
||||
features: [FEATURE.general],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.getStarted', {
|
||||
defaultMessage: 'Getting started',
|
||||
}),
|
||||
],
|
||||
skipUrlState: true,
|
||||
};
|
||||
|
||||
export const detectionResponseLinks: LinkItem = {
|
||||
id: SecurityPageName.detectionAndResponse,
|
||||
title: DETECTION_RESPONSE,
|
||||
path: DETECTION_RESPONSE_PATH,
|
||||
globalNavEnabled: false,
|
||||
experimentalKey: 'detectionResponseEnabled',
|
||||
features: [FEATURE.general],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.detectionAndResponse', {
|
||||
defaultMessage: 'Detection & Response',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export const dashboardsLandingLinks: LinkItem = {
|
||||
id: SecurityPageName.dashboardsLanding,
|
||||
title: DASHBOARDS,
|
||||
path: DASHBOARDS_PATH,
|
||||
globalNavEnabled: false,
|
||||
features: [FEATURE.general],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.dashboards', {
|
||||
defaultMessage: 'Dashboards',
|
||||
}),
|
||||
],
|
||||
links: [overviewLinks, detectionResponseLinks],
|
||||
};
|
|
@ -222,6 +222,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
}
|
||||
licenseService.start(plugins.licensing.license$);
|
||||
const licensing = licenseService.getLicenseInformation$();
|
||||
|
||||
/**
|
||||
* Register deepLinks and pass an appUpdater for each subPlugin, to change deepLinks as needed when licensing changes.
|
||||
*/
|
||||
|
|
34
x-pack/plugins/security_solution/public/timelines/links.ts
Normal file
34
x-pack/plugins/security_solution/public/timelines/links.ts
Normal file
|
@ -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 { i18n } from '@kbn/i18n';
|
||||
import { SecurityPageName, TIMELINES_PATH } from '../../common/constants';
|
||||
import { TIMELINES } from '../app/translations';
|
||||
import { FEATURE, LinkItem } from '../common/links/types';
|
||||
|
||||
export const links: LinkItem = {
|
||||
id: SecurityPageName.timelines,
|
||||
title: TIMELINES,
|
||||
path: TIMELINES_PATH,
|
||||
globalNavEnabled: true,
|
||||
features: [FEATURE.general],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.timelines', {
|
||||
defaultMessage: 'Timelines',
|
||||
}),
|
||||
],
|
||||
globalNavOrder: 9005,
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.timelinesTemplates,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.timeline.templates', {
|
||||
defaultMessage: 'Templates',
|
||||
}),
|
||||
path: `${TIMELINES_PATH}/template`,
|
||||
},
|
||||
],
|
||||
};
|
64
x-pack/plugins/security_solution/public/users/links.ts
Normal file
64
x-pack/plugins/security_solution/public/users/links.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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';
|
||||
import { SecurityPageName, USERS_PATH } from '../../common/constants';
|
||||
import { USERS } from '../app/translations';
|
||||
import { LinkItem } from '../common/links/types';
|
||||
|
||||
export const links: LinkItem = {
|
||||
id: SecurityPageName.users,
|
||||
title: USERS,
|
||||
path: USERS_PATH,
|
||||
globalNavEnabled: true,
|
||||
experimentalKey: 'usersEnabled',
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.users', {
|
||||
defaultMessage: 'Users',
|
||||
}),
|
||||
],
|
||||
globalNavOrder: 9004,
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.usersAuthentications,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.users.authentications', {
|
||||
defaultMessage: 'Authentications',
|
||||
}),
|
||||
path: `${USERS_PATH}/authentications`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.usersAnomalies,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.users.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: `${USERS_PATH}/anomalies`,
|
||||
licenseType: 'gold',
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.usersRisk,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.users.risk', {
|
||||
defaultMessage: 'Users by risk',
|
||||
}),
|
||||
path: `${USERS_PATH}/userRisk`,
|
||||
experimentalKey: 'riskyUsersEnabled',
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.usersEvents,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.users.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
path: `${USERS_PATH}/events`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.usersExternalAlerts,
|
||||
title: i18n.translate('xpack.securitySolution.appLinks.users.externalAlerts', {
|
||||
defaultMessage: 'External Alerts',
|
||||
}),
|
||||
path: `${USERS_PATH}/externalAlerts`,
|
||||
},
|
||||
],
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue