[7.7] [SIEM][Detection Engine] Add validation for Rule Actions (#63332) (#66759)

This commit is contained in:
patrykkopycinski 2020-05-16 12:19:57 +02:00 committed by GitHub
parent f3da8d06a6
commit 75e5edb346
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 615 additions and 64 deletions

View file

@ -4,19 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useCallback, useEffect, useState } from 'react';
import { isEmpty } from 'lodash/fp';
import { EuiSpacer, EuiCallOut } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import deepMerge from 'deepmerge';
import ReactMarkdown from 'react-markdown';
import styled from 'styled-components';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { loadActionTypes } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/lib/action_connector_api';
import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../../common/constants';
import { SelectField } from '../../../../../shared_imports';
import {
ActionForm,
ActionType,
loadActionTypes,
} from '../../../../../../../../../plugins/triggers_actions_ui/public';
import { AlertAction } from '../../../../../../../../../plugins/alerting/common';
import { useKibana } from '../../../../../lib/kibana';
import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../../common/constants';
import { FORM_ERRORS_TITLE } from './translations';
type ThrottleSelectField = typeof SelectField;
@ -24,7 +28,14 @@ const DEFAULT_ACTION_GROUP_ID = 'default';
const DEFAULT_ACTION_MESSAGE =
'Rule {{context.rule.name}} generated {{state.signals_count}} signals';
const FieldErrorsContainer = styled.div`
p {
margin-bottom: 0;
}
`;
export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables }) => {
const [fieldErrors, setFieldErrors] = useState<string | null>(null);
const [supportedActionTypes, setSupportedActionTypes] = useState<ActionType[] | undefined>();
const {
http,
@ -32,13 +43,18 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables
notifications,
} = useKibana().services;
const actions: AlertAction[] = useMemo(
() => (!isEmpty(field.value) ? (field.value as AlertAction[]) : []),
[field.value]
);
const setActionIdByIndex = useCallback(
(id: string, index: number) => {
const updatedActions = [...(field.value as Array<Partial<AlertAction>>)];
const updatedActions = [...(actions as Array<Partial<AlertAction>>)];
updatedActions[index] = deepMerge(updatedActions[index], { id });
field.setValue(updatedActions);
},
[field]
[field.setValue, actions]
);
const setAlertProperty = useCallback(
@ -49,11 +65,11 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables
const setActionParamsProperty = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(key: string, value: any, index: number) => {
const updatedActions = [...(field.value as AlertAction[])];
const updatedActions = [...actions];
updatedActions[index].params[key] = value;
field.setValue(updatedActions);
},
[field]
[field.setValue, actions]
);
useEffect(() => {
@ -66,21 +82,55 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables
})();
}, []);
useEffect(() => {
if (field.form.isSubmitting || !field.errors.length) {
return setFieldErrors(null);
}
if (
field.form.isSubmitted &&
!field.form.isSubmitting &&
field.form.isValid === false &&
field.errors.length
) {
const errorsString = field.errors.map(({ message }) => message).join('\n');
return setFieldErrors(errorsString);
}
}, [
field.form.isSubmitted,
field.form.isSubmitting,
field.isChangingValue,
field.form.isValid,
field.errors,
setFieldErrors,
]);
if (!supportedActionTypes) return <></>;
return (
<ActionForm
actions={field.value as AlertAction[]}
messageVariables={messageVariables}
defaultActionGroupId={DEFAULT_ACTION_GROUP_ID}
setActionIdByIndex={setActionIdByIndex}
setAlertProperty={setAlertProperty}
setActionParamsProperty={setActionParamsProperty}
http={http}
actionTypeRegistry={actionTypeRegistry}
actionTypes={supportedActionTypes}
defaultActionMessage={DEFAULT_ACTION_MESSAGE}
toastNotifications={notifications.toasts}
/>
<>
{fieldErrors ? (
<>
<FieldErrorsContainer>
<EuiCallOut title={FORM_ERRORS_TITLE} color="danger" iconType="alert">
<ReactMarkdown source={fieldErrors} />
</EuiCallOut>
</FieldErrorsContainer>
<EuiSpacer />
</>
) : null}
<ActionForm
actions={actions}
messageVariables={messageVariables}
defaultActionGroupId={DEFAULT_ACTION_GROUP_ID}
setActionIdByIndex={setActionIdByIndex}
setAlertProperty={setAlertProperty}
setActionParamsProperty={setActionParamsProperty}
http={http}
actionTypeRegistry={actionTypeRegistry}
actionTypes={supportedActionTypes}
defaultActionMessage={DEFAULT_ACTION_MESSAGE}
toastNotifications={notifications.toasts}
/>
</>
);
};

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
export const FORM_ERRORS_TITLE = i18n.translate(
'xpack.siem.detectionEngine.createRule.ruleActionsField.ruleActionsFormErrorsTitle',
{
defaultMessage: 'Please fix issues listed below',
}
);

View file

@ -4,7 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer } from '@elastic/eui';
import {
EuiHorizontalRule,
EuiForm,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiSpacer,
} from '@elastic/eui';
import { findIndex } from 'lodash/fp';
import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react';
import deepEqual from 'fast-deep-equal';
@ -20,7 +28,7 @@ import {
} from '../throttle_select_field';
import { RuleActionsField } from '../rule_actions_field';
import { useKibana } from '../../../../../lib/kibana';
import { schema } from './schema';
import { getSchema } from './schema';
import * as I18n from './translations';
interface StepRuleActionsProps extends RuleStepProps {
@ -38,6 +46,15 @@ const stepActionsDefaultValue = {
const GhostFormField = () => <></>;
const getThrottleOptions = (throttle?: string | null) => {
// Add support for throttle options set by the API
if (throttle && findIndex(['value', throttle], THROTTLE_OPTIONS) < 0) {
return [...THROTTLE_OPTIONS, { value: throttle, text: throttle }];
}
return THROTTLE_OPTIONS;
};
const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
addPadding = false,
defaultValues,
@ -50,8 +67,12 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
}) => {
const [myStepData, setMyStepData] = useState<ActionsStepRule>(stepActionsDefaultValue);
const {
services: { application },
services: {
application,
triggers_actions_ui: { actionTypeRegistry },
},
} = useKibana();
const schema = useMemo(() => getSchema({ actionTypeRegistry }), [actionTypeRegistry]);
const { form } = useForm({
defaultValue: myStepData,
@ -100,6 +121,12 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
setMyStepData,
]);
const throttleOptions = useMemo(() => {
const throttle = myStepData.throttle;
return getThrottleOptions(throttle);
}, [myStepData]);
const throttleFieldComponentProps = useMemo(
() => ({
idAria: 'detectionEngineStepRuleActionsThrottle',
@ -108,7 +135,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
hasNoInitialSelection: false,
handleChange: updateThrottle,
euiFieldProps: {
options: THROTTLE_OPTIONS,
options: throttleOptions,
},
}),
[isLoading, updateThrottle]
@ -122,30 +149,39 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
<>
<StepContentWrapper addPadding={!isUpdateView}>
<Form form={form} data-test-subj="stepRuleActions">
<UseField
path="throttle"
component={ThrottleSelectField}
componentProps={throttleFieldComponentProps}
/>
{myStepData.throttle !== stepActionsDefaultValue.throttle && (
<>
<EuiSpacer />
<EuiForm>
<UseField
path="throttle"
component={ThrottleSelectField}
componentProps={throttleFieldComponentProps}
/>
{myStepData.throttle !== stepActionsDefaultValue.throttle ? (
<>
<EuiSpacer />
<UseField
path="actions"
defaultValue={myStepData.actions}
component={RuleActionsField}
componentProps={{
messageVariables: actionMessageParams,
}}
/>
<UseField
path="kibanaSiemAppUrl"
defaultValue={kibanaAbsoluteUrl}
component={GhostFormField}
/>
</>
) : (
<UseField
path="actions"
defaultValue={myStepData.actions}
component={RuleActionsField}
componentProps={{
messageVariables: actionMessageParams,
}}
/>
<UseField
path="kibanaSiemAppUrl"
defaultValue={kibanaAbsoluteUrl}
component={GhostFormField}
/>
</>
)}
<UseField path="enabled" defaultValue={myStepData.enabled} component={GhostFormField} />
)}
<UseField path="enabled" defaultValue={myStepData.enabled} component={GhostFormField} />
</EuiForm>
</Form>
</StepContentWrapper>

View file

@ -0,0 +1,166 @@
/*
* 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 { validateSingleAction, validateRuleActionsField } from './schema';
import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils';
import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock';
import { FormHook } from '../../../../../shared_imports';
jest.mock('./utils');
describe('stepRuleActions schema', () => {
const actionTypeRegistry = actionTypeRegistryMock.create();
describe('validateSingleAction', () => {
it('should validate single action', () => {
(isUuidv4 as jest.Mock).mockReturnValue(true);
(validateActionParams as jest.Mock).mockReturnValue([]);
(validateMustache as jest.Mock).mockReturnValue([]);
expect(
validateSingleAction(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
actionTypeId: '.slack',
params: {},
},
actionTypeRegistry
)
).toHaveLength(0);
});
it('should validate single action with invalid mustache template', () => {
(isUuidv4 as jest.Mock).mockReturnValue(true);
(validateActionParams as jest.Mock).mockReturnValue([]);
(validateMustache as jest.Mock).mockReturnValue(['Message is not valid mustache template']);
const errors = validateSingleAction(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
actionTypeId: '.slack',
params: {
message: '{{{mustache}}',
},
},
actionTypeRegistry
);
expect(errors).toHaveLength(1);
expect(errors[0]).toEqual('Message is not valid mustache template');
});
it('should validate single action with incorrect id', () => {
(isUuidv4 as jest.Mock).mockReturnValue(false);
(validateMustache as jest.Mock).mockReturnValue([]);
(validateActionParams as jest.Mock).mockReturnValue([]);
const errors = validateSingleAction(
{
id: '823d4',
group: 'default',
actionTypeId: '.slack',
params: {},
},
actionTypeRegistry
);
expect(errors).toHaveLength(1);
expect(errors[0]).toEqual('No connector selected');
});
});
describe('validateRuleActionsField', () => {
it('should validate rule actions field', () => {
const validator = validateRuleActionsField(actionTypeRegistry);
const result = validator({
path: '',
value: [],
form: {} as FormHook,
formData: jest.fn(),
errors: [],
});
expect(result).toEqual(undefined);
});
it('should validate incorrect rule actions field', () => {
(getActionTypeName as jest.Mock).mockReturnValue('Slack');
const validator = validateRuleActionsField(actionTypeRegistry);
const result = validator({
path: '',
value: [
{
id: '3',
group: 'default',
actionTypeId: '.slack',
params: {},
},
],
form: {} as FormHook,
formData: jest.fn(),
errors: [],
});
expect(result).toEqual({
code: 'ERR_FIELD_FORMAT',
message: `
**Slack:**
* No connector selected
`,
path: '',
});
});
it('should validate multiple incorrect rule actions field', () => {
(isUuidv4 as jest.Mock).mockReturnValueOnce(false);
(getActionTypeName as jest.Mock).mockReturnValueOnce('Slack');
(isUuidv4 as jest.Mock).mockReturnValueOnce(true);
(getActionTypeName as jest.Mock).mockReturnValueOnce('Pagerduty');
(validateActionParams as jest.Mock).mockReturnValue(['Summary is required']);
(validateMustache as jest.Mock).mockReturnValue(['Component is not valid mustache template']);
const validator = validateRuleActionsField(actionTypeRegistry);
const result = validator({
path: '',
value: [
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
actionTypeId: '.slack',
params: {},
},
{
id: 'a8d1ef21-dcb9-4ac6-9e52-961f938a4c17',
group: 'default',
actionTypeId: '.pagerduty',
params: {
component: '{{{',
},
},
],
form: {} as FormHook,
formData: jest.fn(),
errors: [],
});
expect(result).toEqual({
code: 'ERR_FIELD_FORMAT',
message: `
**Slack:**
* No connector selected
**Pagerduty:**
* Summary is required
* Component is not valid mustache template
`,
path: '',
});
});
});
});

View file

@ -6,9 +6,69 @@
import { i18n } from '@kbn/i18n';
import { FormSchema } from '../../../../../shared_imports';
import {
AlertAction,
ActionTypeRegistryContract,
} from '../../../../../../../../../plugins/triggers_actions_ui/public';
import { FormSchema, FormData, ValidationFunc, ERROR_CODE } from '../../../../../shared_imports';
import * as I18n from './translations';
import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils';
export const schema: FormSchema = {
export const validateSingleAction = (
actionItem: AlertAction,
actionTypeRegistry: ActionTypeRegistryContract
): string[] => {
if (!isUuidv4(actionItem.id)) {
return [I18n.NO_CONNECTOR_SELECTED];
}
const actionParamsErrors = validateActionParams(actionItem, actionTypeRegistry);
const mustacheErrors = validateMustache(actionItem.params);
return [...actionParamsErrors, ...mustacheErrors];
};
export const validateRuleActionsField = (actionTypeRegistry: ActionTypeRegistryContract) => (
...data: Parameters<ValidationFunc>
): ReturnType<ValidationFunc<{}, ERROR_CODE>> | undefined => {
const [{ value, path }] = data as [{ value: AlertAction[]; path: string }];
const errors = value.reduce((acc, actionItem) => {
const errorsArray = validateSingleAction(actionItem, actionTypeRegistry);
if (errorsArray.length) {
const actionTypeName = getActionTypeName(actionItem.actionTypeId);
const errorsListItems = errorsArray.map(error => `* ${error}\n`);
return [...acc, `\n**${actionTypeName}:**\n${errorsListItems.join('')}`];
}
return acc;
}, [] as string[]);
if (errors.length) {
return {
code: 'ERR_FIELD_FORMAT',
path,
message: `${errors.join('\n')}`,
};
}
};
export const getSchema = ({
actionTypeRegistry,
}: {
actionTypeRegistry: ActionTypeRegistryContract;
}): FormSchema<FormData> => ({
actions: {
validations: [
{
validator: validateRuleActionsField(actionTypeRegistry),
},
],
},
enabled: {},
kibanaSiemAppUrl: {},
throttle: {
label: i18n.translate(
'xpack.siem.detectionEngine.createRule.stepRuleActions.fieldThrottleLabel',
@ -24,14 +84,4 @@ export const schema: FormSchema = {
}
),
},
actions: {
label: i18n.translate(
'xpack.siem.detectionEngine.createRule.stepRuleActions.fieldActionsLabel',
{
defaultMessage: 'Actions',
}
),
},
enabled: {},
kibanaSiemAppUrl: {},
};
});

View file

@ -5,17 +5,36 @@
*/
import { i18n } from '@kbn/i18n';
import { startCase } from 'lodash/fp';
export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate(
'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle',
'xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithoutActivatingTitle',
{
defaultMessage: 'Create rule without activating it',
}
);
export const COMPLETE_WITH_ACTIVATING = i18n.translate(
'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle',
'xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithActivatingTitle',
{
defaultMessage: 'Create & activate rule',
}
);
export const NO_CONNECTOR_SELECTED = i18n.translate(
'xpack.siem.detectionEngine.createRule.stepRuleActions.noConnectorSelectedErrorMessage',
{
defaultMessage: 'No connector selected',
}
);
export const INVALID_MUSTACHE_TEMPLATE = (paramKey: string) =>
i18n.translate(
'xpack.siem.detectionEngine.createRule.stepRuleActions.invalidMustacheTemplateErrorMessage',
{
defaultMessage: '{key} is not valid mustache template',
values: {
key: startCase(paramKey),
},
}
);

View file

@ -0,0 +1,142 @@
/*
* 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 { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock';
import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils';
describe('stepRuleActions utils', () => {
describe('isUuidv4', () => {
it('should validate proper uuid v4 value', () => {
expect(isUuidv4('817b8bca-91d1-4729-8ee1-3a83aaafd9d4')).toEqual(true);
});
it('should validate incorrect uuid v4 value', () => {
expect(isUuidv4('ad9d4')).toEqual(false);
});
});
describe('getActionTypeName', () => {
it('should return capitalized action type name', () => {
expect(getActionTypeName('.slack')).toEqual('Slack');
});
it('should return empty string actionTypeId had improper format', () => {
expect(getActionTypeName('slack')).toEqual('');
});
});
describe('validateMustache', () => {
it('should validate mustache template', () => {
expect(
validateMustache({
message: 'Mustache Template {{variable}}',
})
).toHaveLength(0);
});
it('should validate incorrect mustache template', () => {
expect(
validateMustache({
message: 'Mustache Template {{{variable}}',
})
).toHaveLength(1);
});
});
describe('validateActionParams', () => {
const validateParamsMock = jest.fn();
const actionTypeRegistry = actionTypeRegistryMock.create();
beforeAll(() => {
const actionMock = {
id: 'id',
iconClass: 'iconClass',
validateParams: validateParamsMock,
selectMessage: 'message',
validateConnector: jest.fn(),
actionConnectorFields: null,
actionParamsFields: null,
};
actionTypeRegistry.get.mockReturnValue(actionMock);
});
it('should validate action params', () => {
validateParamsMock.mockReturnValue({ errors: [] });
expect(
validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
actionTypeId: '.slack',
params: {
message: 'Message',
},
},
actionTypeRegistry
)
).toHaveLength(0);
});
it('should validate incorrect action params', () => {
validateParamsMock.mockReturnValue({
errors: ['Message is required'],
});
expect(
validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
actionTypeId: '.slack',
params: {},
},
actionTypeRegistry
)
).toHaveLength(1);
});
it('should validate incorrect action params and filter error objects', () => {
validateParamsMock.mockReturnValue({
errors: [
{
message: 'Message is required',
},
],
});
expect(
validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
actionTypeId: '.slack',
params: {},
},
actionTypeRegistry
)
).toHaveLength(0);
});
it('should validate incorrect action params and filter duplicated errors', () => {
validateParamsMock.mockReturnValue({
errors: ['Message is required', 'Message is required', 'Message is required'],
});
expect(
validateActionParams(
{
id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4',
group: 'default',
actionTypeId: '.slack',
params: {},
},
actionTypeRegistry
)
).toHaveLength(1);
});
});
});

View file

@ -0,0 +1,67 @@
/*
* 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 mustache from 'mustache';
import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp';
import {
AlertAction,
ActionTypeRegistryContract,
} from '../../../../../../../../../plugins/triggers_actions_ui/public';
import * as I18n from './translations';
const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
export const isUuidv4 = (id: AlertAction['id']) => !!id.match(UUID_V4_REGEX);
export const getActionTypeName = (actionTypeId: AlertAction['actionTypeId']) => {
if (!actionTypeId) return '';
const actionType = actionTypeId.split('.')[1];
if (!actionType) return '';
return startCase(actionType);
};
export const validateMustache = (params: AlertAction['params']) => {
const errors: string[] = [];
Object.entries(params).forEach(([paramKey, paramValue]) => {
if (!isString(paramValue)) return;
try {
mustache.render(paramValue, {});
} catch (e) {
errors.push(I18n.INVALID_MUSTACHE_TEMPLATE(paramKey));
}
});
return errors;
};
export const validateActionParams = (
actionItem: AlertAction,
actionTypeRegistry: ActionTypeRegistryContract
): string[] => {
const actionErrors = actionTypeRegistry
.get(actionItem.actionTypeId)
?.validateParams(actionItem.params);
if (actionErrors) {
const actionErrorsValues = Object.values(actionErrors.errors);
if (actionErrorsValues.length) {
const filteredObjects: Array<string | string[]> = actionErrorsValues.filter(
item => isString(item) || isArray(item)
) as Array<string | string[]>;
const uniqActionErrors = uniq(flattenDeep(filteredObjects));
if (uniqActionErrors.length) {
return uniqActionErrors;
}
}
}
return [];
};

View file

@ -13324,8 +13324,8 @@
"xpack.siem.detectionEngine.components.importRuleModal.overwriteDescription": "保存されたオブジェクトを同じルールIDで自動的に上書きします",
"xpack.siem.detectionEngine.components.importRuleModal.selectRuleDescription": "インポートする SIEM ルール (検出エンジンビューからエクスポートしたもの) を選択します",
"xpack.siem.detectionEngine.components.importRuleModal.successfullyImportedRulesTitle": "{totalRules} {totalRules, plural, =1 {ルール} other {ルール}}を正常にインポートしました",
"xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle": "ルールの作成と有効化",
"xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle": "有効化せずにルールを作成",
"xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithActivatingTitle": "ルールの作成と有効化",
"xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithoutActivatingTitle": "有効化せずにルールを作成",
"xpack.siem.detectionEngine.createRule.backToRulesDescription": "シグナル検出ルールに戻る",
"xpack.siem.detectionEngine.createRule.editRuleButton": "編集",
"xpack.siem.detectionEngine.createRule.filtersLabel": "フィルター",

View file

@ -13328,8 +13328,8 @@
"xpack.siem.detectionEngine.components.importRuleModal.overwriteDescription": "自动覆盖具有相同规则 ID 的已保存对象",
"xpack.siem.detectionEngine.components.importRuleModal.selectRuleDescription": "选择要导入的 SIEM 规则(如从检测引擎视图导出的)",
"xpack.siem.detectionEngine.components.importRuleModal.successfullyImportedRulesTitle": "已成功导入 {totalRules} 个{totalRules, plural, =1 {规则} other {规则}}",
"xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle": "创建并激活规则",
"xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle": "创建规则但不激活",
"xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithActivatingTitle": "创建并激活规则",
"xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithoutActivatingTitle": "创建规则但不激活",
"xpack.siem.detectionEngine.createRule.backToRulesDescription": "返回到信号检测规则",
"xpack.siem.detectionEngine.createRule.editRuleButton": "编辑",
"xpack.siem.detectionEngine.createRule.filtersLabel": "筛选",

View file

@ -11,11 +11,18 @@ export { AlertsContextProvider } from './application/context/alerts_context';
export { ActionsConnectorsContextProvider } from './application/context/actions_connectors_context';
export { AlertAdd } from './application/sections/alert_form';
export { ActionForm } from './application/sections/action_connector_form';
export { AlertAction, Alert, AlertTypeModel, ActionType } from './types';
export {
AlertAction,
Alert,
AlertTypeModel,
ActionType,
ActionTypeRegistryContract,
} from './types';
export {
ConnectorAddFlyout,
ConnectorEditFlyout,
} from './application/sections/action_connector_form';
export { loadActionTypes } from './application/lib/action_connector_api';
export function plugin(ctx: PluginInitializerContext) {
return new Plugin(ctx);