* update extra action on rule detail to match design * remove experimental label * allow pre-package to be deleted + do not allow wrong user to create pre-packages rules * Additional look back minimum value to 1 * fix flow with edit rule * add success toaster when rule is created or updated * Fix Timeline selector loading * review ben doc + change detectin engine to detection even in url * Succeeded text size consistency in rule details page * fix description of threats * fix test * fix type * fix internatinalization * Update x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts Co-Authored-By: Garrett Spong <spong@users.noreply.github.com> * Update x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts Co-Authored-By: Garrett Spong <spong@users.noreply.github.com> * Update x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx Co-Authored-By: Garrett Spong <spong@users.noreply.github.com> * review I * fix type Co-authored-by: Garrett Spong <spong@users.noreply.github.com> Co-authored-by: Garrett Spong <spong@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
666eda060b
commit
be1c35f4d4
|
@ -55,7 +55,7 @@ export const HeaderGlobal = React.memo<HeaderGlobalProps>(({ hideDetectionEngine
|
|||
display="condensed"
|
||||
navTabs={
|
||||
hideDetectionEngine
|
||||
? pickBy((_, key) => key !== SiemPageName.detectionEngine, navTabs)
|
||||
? pickBy((_, key) => key !== SiemPageName.detections, navTabs)
|
||||
: navTabs
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -61,32 +61,32 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
|
|||
<Route
|
||||
component={RedirectToDetectionEnginePage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detections})`}
|
||||
strict
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToDetectionEnginePage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/:tabName(${DetectionEngineTab.alerts}|${DetectionEngineTab.signals})`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detections})/:tabName(${DetectionEngineTab.alerts}|${DetectionEngineTab.signals})`}
|
||||
strict
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToRulesPage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detections})/rules`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToCreateRulePage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules/create-rule`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detections})/rules/create-rule`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToRuleDetailsPage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules/rule-details`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detections})/rules/rule-details`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToEditRulePage}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/rules/rule-details/edit-rule`}
|
||||
path={`${match.url}/:pageName(${SiemPageName.detections})/rules/rule-details/edit-rule`}
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToTimelinesPage}
|
||||
|
|
|
@ -15,7 +15,7 @@ export type DetectionEngineComponentProps = RouteComponentProps<{
|
|||
search: string;
|
||||
}>;
|
||||
|
||||
export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine';
|
||||
export const DETECTION_ENGINE_PAGE_NAME = 'detections';
|
||||
|
||||
export const RedirectToDetectionEnginePage = ({
|
||||
match: {
|
||||
|
|
|
@ -64,12 +64,12 @@ describe('SIEM Navigation', () => {
|
|||
expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, {
|
||||
detailName: undefined,
|
||||
navTabs: {
|
||||
'detection-engine': {
|
||||
detections: {
|
||||
disabled: false,
|
||||
href: '#/link-to/detection-engine',
|
||||
id: 'detection-engine',
|
||||
name: 'Detection engine',
|
||||
urlKey: 'detection-engine',
|
||||
href: '#/link-to/detections',
|
||||
id: 'detections',
|
||||
name: 'Detections',
|
||||
urlKey: 'detections',
|
||||
},
|
||||
hosts: {
|
||||
disabled: false,
|
||||
|
@ -146,12 +146,12 @@ describe('SIEM Navigation', () => {
|
|||
detailName: undefined,
|
||||
filters: [],
|
||||
navTabs: {
|
||||
'detection-engine': {
|
||||
detections: {
|
||||
disabled: false,
|
||||
href: '#/link-to/detection-engine',
|
||||
id: 'detection-engine',
|
||||
name: 'Detection engine',
|
||||
urlKey: 'detection-engine',
|
||||
href: '#/link-to/detections',
|
||||
id: 'detections',
|
||||
name: 'Detections',
|
||||
urlKey: 'detections',
|
||||
},
|
||||
hosts: {
|
||||
disabled: false,
|
||||
|
|
|
@ -45,11 +45,25 @@ const MyEuiFlexItem = styled(EuiFlexItem)`
|
|||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const EuiSelectableContainer = styled.div`
|
||||
const EuiSelectableContainer = styled.div<{ loading: boolean }>`
|
||||
.euiSelectable {
|
||||
.euiFormControlLayout__childrenWrapper {
|
||||
display: flex;
|
||||
}
|
||||
${({ loading }) => `${
|
||||
loading
|
||||
? `
|
||||
.euiFormControlLayoutIcons {
|
||||
display: none;
|
||||
}
|
||||
.euiFormControlLayoutIcons.euiFormControlLayoutIcons--right {
|
||||
display: block;
|
||||
left: 12px;
|
||||
top: 12px;
|
||||
}`
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -265,7 +279,7 @@ const SearchTimelineSuperSelectComponent: React.FC<SearchTimelineSuperSelectProp
|
|||
onlyUserFavorite={onlyFavorites}
|
||||
>
|
||||
{({ timelines, loading, totalCount }) => (
|
||||
<EuiSelectableContainer>
|
||||
<EuiSelectableContainer loading={loading}>
|
||||
<EuiSelectable
|
||||
height={POPOVER_HEIGHT}
|
||||
isLoading={loading && timelines.length === 0}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
export enum CONSTANTS {
|
||||
appQuery = 'query',
|
||||
detectionEnginePage = 'detectionEngine.page',
|
||||
detectionsPage = 'detections.page',
|
||||
filters = 'filters',
|
||||
hostsDetails = 'hosts.details',
|
||||
hostsPage = 'hosts.page',
|
||||
|
@ -20,4 +20,4 @@ export enum CONSTANTS {
|
|||
unknown = 'unknown',
|
||||
}
|
||||
|
||||
export type UrlStateType = 'detection-engine' | 'host' | 'network' | 'overview' | 'timeline';
|
||||
export type UrlStateType = 'detections' | 'host' | 'network' | 'overview' | 'timeline';
|
||||
|
|
|
@ -4,46 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { navTabs } from '../../pages/home/home_navigations';
|
||||
import { SiemPageName } from '../../pages/home/types';
|
||||
import { isKqlForRoute, getTitle } from './helpers';
|
||||
import { CONSTANTS } from './constants';
|
||||
import { getTitle } from './helpers';
|
||||
import { HostsType } from '../../store/hosts/model';
|
||||
|
||||
describe('Helpers Url_State', () => {
|
||||
describe('isKqlForRoute', () => {
|
||||
test('host page and host page kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsPage);
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
test('host page and host details kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsDetails);
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
test('host details and host details kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsDetails);
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
test('host details and host page kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsPage);
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
test('network page and network page kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkPage);
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
test('network page and network details kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkDetails);
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
test('network details and network details kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.network, '10.100.7.198', CONSTANTS.networkDetails);
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
test('network details and network page kuery', () => {
|
||||
const result = isKqlForRoute(SiemPageName.network, '123.234.34', CONSTANTS.networkPage);
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
describe('getTitle', () => {
|
||||
test('host page name', () => {
|
||||
const result = getTitle('hosts', undefined, navTabs);
|
||||
|
|
|
@ -78,8 +78,8 @@ export const getUrlType = (pageName: string): UrlStateType => {
|
|||
return 'host';
|
||||
} else if (pageName === SiemPageName.network) {
|
||||
return 'network';
|
||||
} else if (pageName === SiemPageName.detectionEngine) {
|
||||
return 'detection-engine';
|
||||
} else if (pageName === SiemPageName.detections) {
|
||||
return 'detections';
|
||||
} else if (pageName === SiemPageName.timelines) {
|
||||
return 'timeline';
|
||||
}
|
||||
|
@ -111,31 +111,14 @@ export const getCurrentLocation = (
|
|||
return CONSTANTS.networkDetails;
|
||||
}
|
||||
return CONSTANTS.networkPage;
|
||||
} else if (pageName === SiemPageName.detectionEngine) {
|
||||
return CONSTANTS.detectionEnginePage;
|
||||
} else if (pageName === SiemPageName.detections) {
|
||||
return CONSTANTS.detectionsPage;
|
||||
} else if (pageName === SiemPageName.timelines) {
|
||||
return CONSTANTS.timelinePage;
|
||||
}
|
||||
return CONSTANTS.unknown;
|
||||
};
|
||||
|
||||
export const isKqlForRoute = (
|
||||
pageName: string,
|
||||
detailName: string | undefined,
|
||||
queryLocation: LocationTypes | null = null
|
||||
): boolean => {
|
||||
const currentLocation = getCurrentLocation(pageName, detailName);
|
||||
if (
|
||||
(currentLocation === CONSTANTS.hostsPage && queryLocation === CONSTANTS.hostsPage) ||
|
||||
(currentLocation === CONSTANTS.networkPage && queryLocation === CONSTANTS.networkPage) ||
|
||||
(currentLocation === CONSTANTS.hostsDetails && queryLocation === CONSTANTS.hostsDetails) ||
|
||||
(currentLocation === CONSTANTS.networkDetails && queryLocation === CONSTANTS.networkDetails)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const makeMapStateToProps = () => {
|
||||
const getInputsSelector = inputsSelectors.inputsSelector();
|
||||
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector();
|
||||
|
|
|
@ -24,7 +24,7 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [
|
|||
];
|
||||
|
||||
export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
|
||||
'detection-engine': [
|
||||
detections: [
|
||||
CONSTANTS.appQuery,
|
||||
CONSTANTS.filters,
|
||||
CONSTANTS.savedQuery,
|
||||
|
@ -56,7 +56,7 @@ export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
|
|||
};
|
||||
|
||||
export type LocationTypes =
|
||||
| CONSTANTS.detectionEnginePage
|
||||
| CONSTANTS.detectionsPage
|
||||
| CONSTANTS.hostsDetails
|
||||
| CONSTANTS.hostsPage
|
||||
| CONSTANTS.networkDetails
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createPrepackagedRules } from './api';
|
||||
|
||||
type Return = [boolean, boolean | null];
|
||||
|
||||
interface UseCreatePackagedRules {
|
||||
canUserCRUD: boolean | null;
|
||||
hasIndexManage: boolean | null;
|
||||
hasManageApiKey: boolean | null;
|
||||
isAuthenticated: boolean | null;
|
||||
isSignalIndexExists: boolean | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for creating the packages rules
|
||||
*
|
||||
* @param canUserCRUD boolean
|
||||
* @param hasIndexManage boolean
|
||||
* @param hasManageApiKey boolean
|
||||
* @param isAuthenticated boolean
|
||||
* @param isSignalIndexExists boolean
|
||||
*
|
||||
* @returns [loading, hasCreatedPackageRules]
|
||||
*/
|
||||
export const useCreatePackagedRules = ({
|
||||
canUserCRUD,
|
||||
hasIndexManage,
|
||||
hasManageApiKey,
|
||||
isAuthenticated,
|
||||
isSignalIndexExists,
|
||||
}: UseCreatePackagedRules): Return => {
|
||||
const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState<boolean | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let isSubscribed = true;
|
||||
const abortCtrl = new AbortController();
|
||||
setLoading(true);
|
||||
|
||||
async function createRules() {
|
||||
try {
|
||||
await createPrepackagedRules({
|
||||
signal: abortCtrl.signal,
|
||||
});
|
||||
|
||||
if (isSubscribed) {
|
||||
setHasCreatedPackageRules(true);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isSubscribed) {
|
||||
setHasCreatedPackageRules(false);
|
||||
}
|
||||
}
|
||||
if (isSubscribed) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (
|
||||
canUserCRUD &&
|
||||
hasIndexManage &&
|
||||
hasManageApiKey &&
|
||||
isAuthenticated &&
|
||||
isSignalIndexExists
|
||||
) {
|
||||
createRules();
|
||||
}
|
||||
return () => {
|
||||
isSubscribed = false;
|
||||
abortCtrl.abort();
|
||||
};
|
||||
}, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]);
|
||||
|
||||
return [loading, hasCreatedPackageRules];
|
||||
};
|
|
@ -8,7 +8,6 @@ import { useEffect, useState, useRef } from 'react';
|
|||
|
||||
import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
|
||||
import { useStateToaster } from '../../../components/toasters';
|
||||
import { createPrepackagedRules } from '../rules';
|
||||
import { createSignalIndex, getSignalIndex } from './api';
|
||||
import * as i18n from './translations';
|
||||
import { PostSignalError, SignalIndexError } from './types';
|
||||
|
@ -41,7 +40,6 @@ export const useSignalIndex = (): Return => {
|
|||
if (isSubscribed && signal != null) {
|
||||
setSignalIndexName(signal.name);
|
||||
setSignalIndexExists(true);
|
||||
createPrepackagedRules({ signal: abortCtrl.signal });
|
||||
}
|
||||
} catch (error) {
|
||||
if (isSubscribed) {
|
||||
|
|
|
@ -45,7 +45,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 1,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -55,7 +55,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 2,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -65,7 +65,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 3,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -76,7 +76,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 4,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -87,7 +87,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 5,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -98,7 +98,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 6,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -109,7 +109,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 7,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -120,7 +120,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 8,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -131,7 +131,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 9,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -142,7 +142,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 10,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -153,7 +153,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 11,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -164,7 +164,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 12,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -175,7 +175,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 13,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -186,7 +186,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 14,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -197,7 +197,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 15,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -208,7 +208,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 16,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -219,7 +219,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 17,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -230,7 +230,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 18,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -241,7 +241,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 19,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -252,7 +252,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 20,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
@ -263,7 +263,7 @@ export const ActivityMonitor = React.memo(() => {
|
|||
{
|
||||
id: 21,
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/rule-details',
|
||||
href: '#/detections/rules/rule-details',
|
||||
name: 'Automated exfiltration',
|
||||
},
|
||||
ran: '2019-12-28 00:00:00.000-05:00',
|
||||
|
|
|
@ -86,7 +86,7 @@ export const STACK_BY_USERS = i18n.translate(
|
|||
export const HISTOGRAM_HEADER = i18n.translate(
|
||||
'xpack.siem.detectionEngine.signals.histogram.headerTitle',
|
||||
{
|
||||
defaultMessage: 'Signal detection frequency',
|
||||
defaultMessage: 'Signal count',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro
|
|||
import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user';
|
||||
import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index';
|
||||
import { useKibana } from '../../../../lib/kibana';
|
||||
import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules';
|
||||
|
||||
export interface State {
|
||||
canUserCRUD: boolean | null;
|
||||
|
@ -161,6 +162,14 @@ export const useUserInfo = (): State => {
|
|||
createSignalIndex,
|
||||
] = useSignalIndex();
|
||||
|
||||
useCreatePackagedRules({
|
||||
canUserCRUD,
|
||||
hasIndexManage,
|
||||
hasManageApiKey,
|
||||
isAuthenticated,
|
||||
isSignalIndexExists,
|
||||
});
|
||||
|
||||
const uiCapabilities = useKibana().services.application.capabilities;
|
||||
const capabilitiesCanUserCRUD: boolean =
|
||||
typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false;
|
||||
|
|
|
@ -148,7 +148,7 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
|
|||
}
|
||||
title={i18n.PAGE_TITLE}
|
||||
>
|
||||
<EuiButton fill href="#/detection-engine/rules" iconType="gear">
|
||||
<EuiButton fill href="#/detections/rules" iconType="gear">
|
||||
{i18n.BUTTON_MANAGE_RULES}
|
||||
</EuiButton>
|
||||
</HeaderPage>
|
||||
|
|
|
@ -15,7 +15,7 @@ import { RuleDetails } from './rules/details';
|
|||
import { RulesComponent } from './rules';
|
||||
import { DetectionEngineTab } from './types';
|
||||
|
||||
const detectionEnginePath = `/:pageName(detection-engine)`;
|
||||
const detectionEnginePath = `/:pageName(detections)`;
|
||||
|
||||
type Props = Partial<RouteComponentProps<{}>> & { url: string };
|
||||
|
||||
|
@ -42,12 +42,9 @@ export const DetectionEngineContainer = React.memo<Props>(() => (
|
|||
<EditRuleComponent />
|
||||
</Route>
|
||||
<Route
|
||||
path="/detection-engine/"
|
||||
path="/detections/"
|
||||
render={({ location: { search = '' } }) => (
|
||||
<Redirect
|
||||
from="/detection-engine/"
|
||||
to={`/detection-engine/${DetectionEngineTab.signals}${search}`}
|
||||
/>
|
||||
<Redirect from="/detections/" to={`/detections/${DetectionEngineTab.signals}${search}`} />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
|
|
|
@ -60,7 +60,7 @@ export const mockTableData: TableData[] = [
|
|||
lastResponse: { type: '—' },
|
||||
method: 'saved_query',
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61',
|
||||
href: '#/detections/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61',
|
||||
name: 'Home Grown!',
|
||||
status: 'Status Placeholder',
|
||||
},
|
||||
|
@ -112,7 +112,7 @@ export const mockTableData: TableData[] = [
|
|||
lastResponse: { type: '—' },
|
||||
method: 'saved_query',
|
||||
rule: {
|
||||
href: '#/detection-engine/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee',
|
||||
href: '#/detections/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee',
|
||||
name: 'Home Grown!',
|
||||
status: 'Status Placeholder',
|
||||
},
|
||||
|
|
|
@ -90,7 +90,7 @@ export const getColumns = (
|
|||
field: 'method',
|
||||
name: i18n.COLUMN_METHOD,
|
||||
truncateText: true,
|
||||
width: '16%',
|
||||
width: '14%',
|
||||
},
|
||||
{
|
||||
field: 'severity',
|
||||
|
@ -165,7 +165,7 @@ export const getColumns = (
|
|||
/>
|
||||
),
|
||||
sortable: true,
|
||||
width: '85px',
|
||||
width: '95px',
|
||||
},
|
||||
];
|
||||
const actions: RulesColumns[] = [
|
||||
|
|
|
@ -24,7 +24,7 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[]
|
|||
immutable: rule.immutable,
|
||||
rule_id: rule.rule_id,
|
||||
rule: {
|
||||
href: `#/detection-engine/rules/id/${encodeURIComponent(rule.id)}`,
|
||||
href: `#/detections/rules/id/${encodeURIComponent(rule.id)}`,
|
||||
name: rule.name,
|
||||
status: 'Status Placeholder',
|
||||
},
|
||||
|
|
|
@ -125,7 +125,7 @@ export const buildThreatsDescription = ({
|
|||
description: (
|
||||
<ThreatsEuiFlexGroup direction="column">
|
||||
{threats.map((threat, index) => {
|
||||
const tactic = tacticsOptions.find(t => t.name === threat.tactic.name);
|
||||
const tactic = tacticsOptions.find(t => t.id === threat.tactic.id);
|
||||
return (
|
||||
<EuiFlexItem key={`${threat.tactic.name}-${index}`}>
|
||||
<EuiLink href={threat.tactic.reference} target="_blank">
|
||||
|
@ -133,7 +133,7 @@ export const buildThreatsDescription = ({
|
|||
</EuiLink>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="flexStart" direction="column">
|
||||
{threat.techniques.map(technique => {
|
||||
const myTechnique = techniquesOptions.find(t => t.name === technique.name);
|
||||
const myTechnique = techniquesOptions.find(t => t.id === technique.id);
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<TechniqueLinkItem
|
||||
|
|
|
@ -10,7 +10,7 @@ exports[`RuleActionsOverflow renders correctly against snapshot 1`] = `
|
|||
delay="regular"
|
||||
position="top"
|
||||
>
|
||||
<EuiButtonIcon
|
||||
<ForwardRef(Styled(EuiButtonIcon))
|
||||
aria-label="All actions"
|
||||
iconType="boxesHorizontal"
|
||||
isDisabled={false}
|
||||
|
|
|
@ -12,17 +12,29 @@ import {
|
|||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { noop } from 'lodash/fp';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Rule } from '../../../../../containers/detection_engine/rules';
|
||||
import * as i18n from './translations';
|
||||
import * as i18nActions from '../../../rules/translations';
|
||||
import { deleteRulesAction, duplicateRulesAction } from '../../all/actions';
|
||||
import { displaySuccessToast, useStateToaster } from '../../../../../components/toasters';
|
||||
import { deleteRulesAction, duplicateRulesAction } from '../../all/actions';
|
||||
import { RuleDownloader } from '../rule_downloader';
|
||||
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine';
|
||||
|
||||
const MyEuiButtonIcon = styled(EuiButtonIcon)`
|
||||
&.euiButtonIcon {
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
border: 1px solid ${({ theme }) => theme.euiColorPrimary};
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface RuleActionsOverflowComponentProps {
|
||||
rule: Rule | null;
|
||||
userHasNoPermissions: boolean;
|
||||
|
@ -86,20 +98,29 @@ const RuleActionsOverflowComponent = ({
|
|||
[rule, userHasNoPermissions]
|
||||
);
|
||||
|
||||
const handlePopoverOpen = useCallback(() => {
|
||||
setIsPopoverOpen(!isPopoverOpen);
|
||||
}, [setIsPopoverOpen, isPopoverOpen]);
|
||||
|
||||
const button = useMemo(
|
||||
() => (
|
||||
<EuiToolTip position="top" content={i18n.ALL_ACTIONS}>
|
||||
<MyEuiButtonIcon
|
||||
iconType="boxesHorizontal"
|
||||
aria-label={i18n.ALL_ACTIONS}
|
||||
isDisabled={userHasNoPermissions}
|
||||
onClick={handlePopoverOpen}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
),
|
||||
[handlePopoverOpen, userHasNoPermissions]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
anchorPosition="leftCenter"
|
||||
button={
|
||||
<EuiToolTip position="top" content={i18n.ALL_ACTIONS}>
|
||||
<EuiButtonIcon
|
||||
iconType="boxesHorizontal"
|
||||
aria-label={i18n.ALL_ACTIONS}
|
||||
isDisabled={userHasNoPermissions}
|
||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
button={button}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
id="ruleActionsOverflow"
|
||||
isOpen={isPopoverOpen}
|
||||
|
|
|
@ -150,7 +150,13 @@ export const ScheduleItem = ({
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber fullWidth min={0} onChange={onChangeTimeVal} value={timeVal} {...rest} />
|
||||
<EuiFieldNumber
|
||||
fullWidth
|
||||
min={minimumValue}
|
||||
onChange={onChangeTimeVal}
|
||||
value={timeVal}
|
||||
{...rest}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
</StyledEuiFormRow>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { AboutStepRule } from '../../types';
|
||||
import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/search_super_select/translations';
|
||||
|
||||
export const threatsDefault = [
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ export const stepAboutDefaultValue: AboutStepRule = {
|
|||
tags: [],
|
||||
timeline: {
|
||||
id: null,
|
||||
title: null,
|
||||
title: DEFAULT_TIMELINE_TITLE,
|
||||
},
|
||||
threats: threatsDefault,
|
||||
};
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
|
||||
import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { isEqual, get } from 'lodash/fp';
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { setFieldValue } from '../../helpers';
|
||||
import { RuleStepProps, RuleStep, AboutStepRule } from '../../types';
|
||||
import * as RuleI18n from '../../translations';
|
||||
import { AddItem } from '../add_item_form';
|
||||
|
@ -71,14 +72,7 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
|
|||
isNew: false,
|
||||
};
|
||||
setMyStepData(myDefaultValues);
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
setFieldValue(form, schema, myDefaultValues);
|
||||
}
|
||||
}, [defaultValues]);
|
||||
|
||||
|
@ -88,7 +82,7 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
|
|||
}
|
||||
}, [form]);
|
||||
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
return isReadOnlyView && myStepData.name != null ? (
|
||||
<StepContentWrapper addPadding={addPadding}>
|
||||
<StepRuleDescription direction={descriptionDirection} schema={schema} data={myStepData} />
|
||||
</StepContentWrapper>
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { isEmpty, isEqual, get } from 'lodash/fp';
|
||||
import { isEmpty, isEqual } from 'lodash/fp';
|
||||
import React, { FC, memo, useCallback, useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -19,6 +19,7 @@ import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/pu
|
|||
import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules';
|
||||
import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants';
|
||||
import { useUiSetting$ } from '../../../../../lib/kibana';
|
||||
import { setFieldValue } from '../../helpers';
|
||||
import * as RuleI18n from '../../translations';
|
||||
import { DefineStepRule, RuleStep, RuleStepProps } from '../../types';
|
||||
import { StepRuleDescription } from '../description_step';
|
||||
|
@ -121,14 +122,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
if (!isEqual(myDefaultValues, myStepData)) {
|
||||
setMyStepData(myDefaultValues);
|
||||
setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig));
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
setFieldValue(form, schema, myDefaultValues);
|
||||
}
|
||||
}
|
||||
}, [defaultValues, indicesConfig]);
|
||||
|
@ -152,7 +146,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
setOpenTimelineSearch(false);
|
||||
}, []);
|
||||
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
return isReadOnlyView && myStepData?.queryBar != null ? (
|
||||
<StepContentWrapper addPadding={addPadding}>
|
||||
<StepRuleDescription
|
||||
direction={descriptionDirection}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
import { isEqual, get } from 'lodash/fp';
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { setFieldValue } from '../../helpers';
|
||||
import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types';
|
||||
import { StepRuleDescription } from '../description_step';
|
||||
import { ScheduleItem } from '../schedule_item_form';
|
||||
|
@ -24,7 +25,7 @@ const stepScheduleDefaultValue = {
|
|||
enabled: true,
|
||||
interval: '5m',
|
||||
isNew: true,
|
||||
from: '0m',
|
||||
from: '1m',
|
||||
};
|
||||
|
||||
const StepScheduleRuleComponent: FC<StepScheduleRuleProps> = ({
|
||||
|
@ -67,14 +68,7 @@ const StepScheduleRuleComponent: FC<StepScheduleRuleProps> = ({
|
|||
isNew: false,
|
||||
};
|
||||
setMyStepData(myDefaultValues);
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
setFieldValue(form, schema, myDefaultValues);
|
||||
}
|
||||
}, [defaultValues]);
|
||||
|
||||
|
|
|
@ -14,13 +14,14 @@ export const schema: FormSchema = {
|
|||
label: i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalLabel',
|
||||
{
|
||||
defaultMessage: 'Rule run interval & look-back',
|
||||
defaultMessage: 'Runs every',
|
||||
}
|
||||
),
|
||||
helpText: i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText',
|
||||
{
|
||||
defaultMessage: 'How often and how far back this rule will search specified indices.',
|
||||
defaultMessage:
|
||||
'Rules run periodically and detect signals within the specified time frame.',
|
||||
}
|
||||
),
|
||||
},
|
||||
|
@ -28,15 +29,14 @@ export const schema: FormSchema = {
|
|||
label: i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackLabel',
|
||||
{
|
||||
defaultMessage: 'Additional look-back',
|
||||
defaultMessage: 'Additional look-back time',
|
||||
}
|
||||
),
|
||||
labelAppend: OptionalFieldLabel,
|
||||
helpText: i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Add more time to the look-back range in order to prevent potential gaps in signal reporting.',
|
||||
defaultMessage: 'Adds time to the look-back period to prevent missed signals.',
|
||||
}
|
||||
),
|
||||
},
|
||||
|
|
|
@ -9,13 +9,13 @@ import { i18n } from '@kbn/i18n';
|
|||
export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle',
|
||||
{
|
||||
defaultMessage: 'Complete rule without activating',
|
||||
defaultMessage: 'Create rule without activating it',
|
||||
}
|
||||
);
|
||||
|
||||
export const COMPLETE_WITH_ACTIVATING = i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle',
|
||||
{
|
||||
defaultMessage: 'Complete rule & activate',
|
||||
defaultMessage: 'Create & activate rule',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -9,10 +9,11 @@ import React, { useCallback, useRef, useState } from 'react';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { usePersistRule } from '../../../../containers/detection_engine/rules';
|
||||
import { HeaderPage } from '../../../../components/header_page';
|
||||
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine';
|
||||
import { WrapperPage } from '../../../../components/wrapper_page';
|
||||
import { usePersistRule } from '../../../../containers/detection_engine/rules';
|
||||
import { displaySuccessToast, useStateToaster } from '../../../../components/toasters';
|
||||
import { SpyRoute } from '../../../../utils/route/spy_routes';
|
||||
import { useUserInfo } from '../../components/user_info';
|
||||
import { AccordionTitle } from '../components/accordion_title';
|
||||
|
@ -55,6 +56,7 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
canUserCRUD,
|
||||
hasManageApiKey,
|
||||
} = useUserInfo();
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
const [openAccordionId, setOpenAccordionId] = useState<RuleStep>(RuleStep.defineRule);
|
||||
const defineRuleRef = useRef<EuiAccordion | null>(null);
|
||||
const aboutRuleRef = useRef<EuiAccordion | null>(null);
|
||||
|
@ -95,6 +97,7 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item);
|
||||
if ([0, 1].includes(stepRuleIdx)) {
|
||||
if (isStepRuleInReadOnlyView[stepsRuleOrder[stepRuleIdx + 1]]) {
|
||||
setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]);
|
||||
setIsStepRuleInEditView({
|
||||
...isStepRuleInReadOnlyView,
|
||||
[step]: true,
|
||||
|
@ -203,12 +206,15 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
async (id: RuleStep) => {
|
||||
const activeForm = await stepsForm.current[openAccordionId]?.submit();
|
||||
if (activeForm != null && activeForm?.isValid) {
|
||||
stepsData.current[openAccordionId] = {
|
||||
...stepsData.current[openAccordionId],
|
||||
data: activeForm.data,
|
||||
isValid: activeForm.isValid,
|
||||
};
|
||||
setOpenAccordionId(id);
|
||||
openCloseAccordion(openAccordionId);
|
||||
|
||||
setIsStepRuleInEditView({
|
||||
...isStepRuleInReadOnlyView,
|
||||
[openAccordionId]: openAccordionId === RuleStep.scheduleRule ? false : true,
|
||||
[openAccordionId]: true,
|
||||
[id]: false,
|
||||
});
|
||||
}
|
||||
|
@ -217,6 +223,8 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
);
|
||||
|
||||
if (isSaved) {
|
||||
const ruleName = (stepsData.current[RuleStep.aboutRule].data as AboutStepRule).name;
|
||||
displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster);
|
||||
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}/rules`} />;
|
||||
}
|
||||
|
||||
|
@ -224,7 +232,7 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
<>
|
||||
<WrapperPage restrictWidth>
|
||||
<HeaderPage
|
||||
backOptions={{ href: '#detection-engine/rules', text: 'Back to rules' }}
|
||||
backOptions={{ href: '#detections/rules', text: i18n.BACK_TO_RULES }}
|
||||
border
|
||||
isLoading={isLoading || loading}
|
||||
title={i18n.PAGE_TITLE}
|
||||
|
@ -252,6 +260,9 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
<EuiHorizontalRule margin="m" />
|
||||
<StepDefineRule
|
||||
addPadding={true}
|
||||
defaultValues={
|
||||
(stepsData.current[RuleStep.defineRule].data as DefineStepRule) ?? null
|
||||
}
|
||||
isReadOnlyView={isStepRuleInReadOnlyView[RuleStep.defineRule]}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setStepsForm}
|
||||
|
@ -284,6 +295,7 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
<EuiHorizontalRule margin="m" />
|
||||
<StepAboutRule
|
||||
addPadding={true}
|
||||
defaultValues={(stepsData.current[RuleStep.aboutRule].data as AboutStepRule) ?? null}
|
||||
descriptionDirection="row"
|
||||
isReadOnlyView={isStepRuleInReadOnlyView[RuleStep.aboutRule]}
|
||||
isLoading={isLoading || loading}
|
||||
|
@ -316,6 +328,9 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
<EuiHorizontalRule margin="m" />
|
||||
<StepScheduleRule
|
||||
addPadding={true}
|
||||
defaultValues={
|
||||
(stepsData.current[RuleStep.scheduleRule].data as ScheduleStepRule) ?? null
|
||||
}
|
||||
descriptionDirection="row"
|
||||
isReadOnlyView={isStepRuleInReadOnlyView[RuleStep.scheduleRule]}
|
||||
isLoading={isLoading || loading}
|
||||
|
|
|
@ -10,6 +10,19 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule.
|
|||
defaultMessage: 'Create new rule',
|
||||
});
|
||||
|
||||
export const BACK_TO_RULES = i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.backToRulesDescription',
|
||||
{
|
||||
defaultMessage: 'Back to signal detection rules',
|
||||
}
|
||||
);
|
||||
|
||||
export const EDIT_RULE = i18n.translate('xpack.siem.detectionEngine.createRule.editRuleButton', {
|
||||
defaultMessage: 'Edit',
|
||||
});
|
||||
|
||||
export const SUCCESSFULLY_CREATED_RULES = (ruleName: string) =>
|
||||
i18n.translate('xpack.siem.detectionEngine.rules.create.successfullyCreatedRuleTitle', {
|
||||
values: { ruleName },
|
||||
defaultMessage: '{ruleName} was created',
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
EuiSpacer,
|
||||
EuiHealth,
|
||||
EuiTab,
|
||||
EuiText,
|
||||
EuiTabs,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -249,7 +250,6 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
|
|||
href: `#${DETECTION_ENGINE_PAGE_NAME}/rules`,
|
||||
text: i18n.BACK_TO_RULES,
|
||||
}}
|
||||
badgeOptions={{ text: i18n.EXPERIMENTAL }}
|
||||
border
|
||||
subtitle={subTitle}
|
||||
subtitle2={[
|
||||
|
@ -273,7 +273,7 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHealth color={statusColor}>
|
||||
{rule?.status ?? getEmptyTagValue()}
|
||||
<EuiText size="xs">{rule?.status ?? getEmptyTagValue()}</EuiText>
|
||||
</EuiHealth>
|
||||
</EuiFlexItem>
|
||||
{rule?.status_date && (
|
||||
|
|
|
@ -13,7 +13,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.ruleDetails
|
|||
export const BACK_TO_RULES = i18n.translate(
|
||||
'xpack.siem.detectionEngine.ruleDetails.backToRulesDescription',
|
||||
{
|
||||
defaultMessage: 'Back to rules',
|
||||
defaultMessage: 'Back to signal detection rules',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -17,11 +17,12 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Redirect, useParams } from 'react-router-dom';
|
||||
|
||||
import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules';
|
||||
import { HeaderPage } from '../../../../components/header_page';
|
||||
import { WrapperPage } from '../../../../components/wrapper_page';
|
||||
import { SpyRoute } from '../../../../utils/route/spy_routes';
|
||||
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine';
|
||||
import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules';
|
||||
import { displaySuccessToast, useStateToaster } from '../../../../components/toasters';
|
||||
import { SpyRoute } from '../../../../utils/route/spy_routes';
|
||||
import { useUserInfo } from '../../components/user_info';
|
||||
import { FormHook, FormData } from '../components/shared_imports';
|
||||
import { StepPanel } from '../components/step_panel';
|
||||
|
@ -48,6 +49,7 @@ interface ScheduleStepRuleForm extends StepRuleForm {
|
|||
}
|
||||
|
||||
export const EditRuleComponent = memo(() => {
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
const {
|
||||
loading: initLoading,
|
||||
isSignalIndexExists,
|
||||
|
@ -271,6 +273,7 @@ export const EditRuleComponent = memo(() => {
|
|||
}, []);
|
||||
|
||||
if (isSaved || (rule != null && rule.immutable)) {
|
||||
displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster);
|
||||
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${ruleId}`} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,3 +28,9 @@ export const SORRY_ERRORS = i18n.translate(
|
|||
export const BACK_TO = i18n.translate('xpack.siem.detectionEngine.editRule.backToDescription', {
|
||||
defaultMessage: 'Back to',
|
||||
});
|
||||
|
||||
export const SUCCESSFULLY_SAVED_RULE = (ruleName: string) =>
|
||||
i18n.translate('xpack.siem.detectionEngine.rules.update.successfullySavedRuleTitle', {
|
||||
values: { ruleName },
|
||||
defaultMessage: '{ruleName} was saved',
|
||||
});
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { pick } from 'lodash/fp';
|
||||
import { get, pick } from 'lodash/fp';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { esFilters } from '../../../../../../../../src/plugins/data/public';
|
||||
import { Rule } from '../../../containers/detection_engine/rules';
|
||||
import { FormData, FormHook, FormSchema } from './components/shared_imports';
|
||||
import { AboutStepRule, DefineStepRule, IMitreEnterpriseAttack, ScheduleStepRule } from './types';
|
||||
|
||||
interface GetStepsData {
|
||||
|
@ -67,3 +68,15 @@ export const getStepsData = ({
|
|||
};
|
||||
|
||||
export const useQuery = () => new URLSearchParams(useLocation().search);
|
||||
|
||||
export const setFieldValue = (
|
||||
form: FormHook<FormData>,
|
||||
schema: FormSchema<FormData>,
|
||||
defaultValues: unknown
|
||||
) =>
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, defaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
export const BACK_TO_DETECTION_ENGINE = i18n.translate(
|
||||
'xpack.siem.detectionEngine.rules.backOptionsHeader',
|
||||
{
|
||||
defaultMessage: 'Back to detection engine',
|
||||
defaultMessage: 'Back to detections',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -18,7 +18,7 @@ export const IMPORT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.impo
|
|||
});
|
||||
|
||||
export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.addNewRuleTitle', {
|
||||
defaultMessage: 'Add new rule',
|
||||
defaultMessage: 'Create new rule',
|
||||
});
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', {
|
||||
|
@ -32,7 +32,7 @@ export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules
|
|||
export const BATCH_ACTIONS = i18n.translate(
|
||||
'xpack.siem.detectionEngine.rules.allRules.batchActionsTitle',
|
||||
{
|
||||
defaultMessage: 'Batch actions',
|
||||
defaultMessage: 'Bulk actions',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -243,7 +243,7 @@ export const COLUMN_TAGS = i18n.translate(
|
|||
export const COLUMN_ACTIVATE = i18n.translate(
|
||||
'xpack.siem.detectionEngine.rules.allRules.columns.activateTitle',
|
||||
{
|
||||
defaultMessage: 'Activate',
|
||||
defaultMessage: 'Activated',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', {
|
||||
defaultMessage: 'Detection engine',
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.detectionsPageTitle', {
|
||||
defaultMessage: 'Detections',
|
||||
});
|
||||
|
||||
export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', {
|
||||
|
|
|
@ -36,12 +36,12 @@ export const navTabs: SiemNavTab = {
|
|||
disabled: false,
|
||||
urlKey: 'network',
|
||||
},
|
||||
[SiemPageName.detectionEngine]: {
|
||||
id: SiemPageName.detectionEngine,
|
||||
[SiemPageName.detections]: {
|
||||
id: SiemPageName.detections,
|
||||
name: i18n.DETECTION_ENGINE,
|
||||
href: getDetectionEngineUrl(),
|
||||
disabled: false,
|
||||
urlKey: 'detection-engine',
|
||||
urlKey: 'detections',
|
||||
},
|
||||
[SiemPageName.timelines]: {
|
||||
id: SiemPageName.timelines,
|
||||
|
|
|
@ -105,7 +105,7 @@ export const HomePage: React.FC = () => (
|
|||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`/:pageName(${SiemPageName.detectionEngine})`}
|
||||
path={`/:pageName(${SiemPageName.detections})`}
|
||||
render={({ location, match }) => (
|
||||
<DetectionEngineContainer location={location} url={match.url} />
|
||||
)}
|
||||
|
|
|
@ -19,7 +19,7 @@ export const NETWORK = i18n.translate('xpack.siem.navigation.network', {
|
|||
});
|
||||
|
||||
export const DETECTION_ENGINE = i18n.translate('xpack.siem.navigation.detectionEngine', {
|
||||
defaultMessage: 'Detection engine',
|
||||
defaultMessage: 'Detections',
|
||||
});
|
||||
|
||||
export const TIMELINES = i18n.translate('xpack.siem.navigation.timelines', {
|
||||
|
|
|
@ -10,7 +10,7 @@ export enum SiemPageName {
|
|||
overview = 'overview',
|
||||
hosts = 'hosts',
|
||||
network = 'network',
|
||||
detectionEngine = 'detection-engine',
|
||||
detections = 'detections',
|
||||
timelines = 'timelines',
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ export type SiemNavTabKey =
|
|||
| SiemPageName.overview
|
||||
| SiemPageName.hosts
|
||||
| SiemPageName.network
|
||||
| SiemPageName.detectionEngine
|
||||
| SiemPageName.detections
|
||||
| SiemPageName.timelines;
|
||||
|
||||
export type SiemNavTab = Record<SiemNavTabKey, NavTab>;
|
||||
|
|
Loading…
Reference in a new issue