mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security solution][Endpoint] New Event Filters sub-section under Administration area (#97903)
* Add Event Filters section to the Admin area (behind feature flag) * new `PaginatedContent` generic component * Refactor Trusted Apps grid view to use PaginatedContent * Refactor usages of `getTestId()` to use new hook
This commit is contained in:
parent
c9ce295a0b
commit
485692dbf1
27 changed files with 1752 additions and 751 deletions
|
@ -102,16 +102,17 @@ ItemDetailsAction.displayName = 'ItemDetailsAction';
|
|||
|
||||
export type ItemDetailsCardProps = PropsWithChildren<{
|
||||
'data-test-subj'?: string;
|
||||
className?: string;
|
||||
}>;
|
||||
export const ItemDetailsCard = memo<ItemDetailsCardProps>(
|
||||
({ children, 'data-test-subj': dataTestSubj }) => {
|
||||
({ children, 'data-test-subj': dataTestSubj, className }) => {
|
||||
const childElements = useMemo(
|
||||
() => groupChildrenByType(children, [ItemDetailsPropertySummary, ItemDetailsAction]),
|
||||
[children]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel paddingSize="none" data-test-subj={dataTestSubj}>
|
||||
<EuiPanel paddingSize="none" data-test-subj={dataTestSubj} className={className}>
|
||||
<EuiFlexGroup direction="row">
|
||||
<SummarySection grow={2}>
|
||||
<EuiDescriptionList compressed type="column">
|
||||
|
|
|
@ -39,6 +39,10 @@ export const mockGlobalState: State = {
|
|||
{ id: 'error-id-1', title: 'title-1', message: ['error-message-1'] },
|
||||
{ id: 'error-id-2', title: 'title-2', message: ['error-message-2'] },
|
||||
],
|
||||
enableExperimental: {
|
||||
eventFilteringEnabled: false,
|
||||
trustedAppsByPolicyEnabled: false,
|
||||
},
|
||||
},
|
||||
hosts: {
|
||||
page: {
|
||||
|
|
|
@ -16,6 +16,7 @@ export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH
|
|||
export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})`;
|
||||
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`;
|
||||
export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`;
|
||||
export const MANAGEMENT_ROUTING_EVENT_FILTERS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.eventFilters})`;
|
||||
|
||||
// --[ STORE ]---------------------------------------------------------------------------
|
||||
/** The SIEM global store namespace where the management state will be mounted */
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
MANAGEMENT_PAGE_SIZE_OPTIONS,
|
||||
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
|
||||
MANAGEMENT_ROUTING_EVENT_FILTERS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICIES_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH,
|
||||
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
|
||||
|
@ -23,6 +24,7 @@ import { AdministrationSubTab } from '../types';
|
|||
import { appendSearch } from '../../common/components/link_to/helpers';
|
||||
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
|
||||
import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state';
|
||||
import { EventFiltersListPageUrlSearchParams } from '../pages/event_filters/types';
|
||||
|
||||
// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
|
||||
type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never ? T1 : never;
|
||||
|
@ -178,3 +180,13 @@ export const getTrustedAppsListPath = (location?: Partial<TrustedAppsListPageLoc
|
|||
querystring.stringify(normalizeTrustedAppsPageLocation(location))
|
||||
)}`;
|
||||
};
|
||||
|
||||
export const getEventFiltersListPath = (
|
||||
location?: Partial<EventFiltersListPageUrlSearchParams>
|
||||
): string => {
|
||||
const path = generatePath(MANAGEMENT_ROUTING_EVENT_FILTERS_PATH, {
|
||||
tabName: AdministrationSubTab.eventFilters,
|
||||
});
|
||||
|
||||
return `${path}${appendSearch(querystring.stringify(location))}`;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,10 @@ export const TRUSTED_APPS_TAB = i18n.translate('xpack.securitySolution.trustedAp
|
|||
defaultMessage: 'Trusted applications',
|
||||
});
|
||||
|
||||
export const EVENT_FILTERS_TAB = i18n.translate('xpack.securitySolution.eventFiltersTab', {
|
||||
defaultMessage: 'Event filters',
|
||||
});
|
||||
|
||||
export const BETA_BADGE_LABEL = i18n.translate('xpack.securitySolution.administration.list.beta', {
|
||||
defaultMessage: 'Beta',
|
||||
});
|
||||
|
|
|
@ -14,8 +14,18 @@ import { HeaderPage } from '../../common/components/header_page';
|
|||
import { SiemNavigation } from '../../common/components/navigation';
|
||||
import { SpyRoute } from '../../common/utils/route/spy_routes';
|
||||
import { AdministrationSubTab } from '../types';
|
||||
import { ENDPOINTS_TAB, TRUSTED_APPS_TAB, BETA_BADGE_LABEL } from '../common/translations';
|
||||
import { getEndpointListPath, getTrustedAppsListPath } from '../common/routing';
|
||||
import {
|
||||
ENDPOINTS_TAB,
|
||||
TRUSTED_APPS_TAB,
|
||||
BETA_BADGE_LABEL,
|
||||
EVENT_FILTERS_TAB,
|
||||
} from '../common/translations';
|
||||
import {
|
||||
getEndpointListPath,
|
||||
getEventFiltersListPath,
|
||||
getTrustedAppsListPath,
|
||||
} from '../common/routing';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
|
||||
|
||||
/** Ensure that all flyouts z-index in Administation area show the flyout header */
|
||||
const EuiPanelStyled = styled(EuiPanel)`
|
||||
|
@ -34,6 +44,7 @@ interface AdministrationListPageProps {
|
|||
|
||||
export const AdministrationListPage: FC<AdministrationListPageProps & CommonProps> = memo(
|
||||
({ beta, title, subtitle, actions, children, headerBackComponent, ...otherProps }) => {
|
||||
const isEventFilteringEnabled = useIsExperimentalFeatureEnabled('eventFilteringEnabled');
|
||||
const badgeOptions = !beta ? undefined : { beta: true, text: BETA_BADGE_LABEL };
|
||||
|
||||
return (
|
||||
|
@ -66,6 +77,18 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp
|
|||
pageId: SecurityPageName.administration,
|
||||
disabled: false,
|
||||
},
|
||||
...(isEventFilteringEnabled
|
||||
? {
|
||||
[AdministrationSubTab.eventFilters]: {
|
||||
name: EVENT_FILTERS_TAB,
|
||||
id: AdministrationSubTab.eventFilters,
|
||||
href: getEventFiltersListPath(),
|
||||
urlKey: 'administration',
|
||||
pageId: SecurityPageName.administration,
|
||||
disabled: false,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
|
||||
|
|
|
@ -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 { useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* Returns a callback that can be used to generate new test ids (values for `data-test-subj`) that
|
||||
* are prefix with a standard string. Will only generate test ids if a prefix is defiened.
|
||||
* Use it in complex component where you might want to expose a `data-test-subj` prop and use that
|
||||
* as a prefix to several other test ids inside of the complex component.
|
||||
*
|
||||
* @example
|
||||
* // `props['data-test-subj'] = 'abc';
|
||||
* const getTestId = useTestIdGenerator(props['data-test-subj']);
|
||||
* getTestId('body'); // abc-body
|
||||
* getTestId('some-other-ui-section'); // abc-some-other-ui-section
|
||||
*
|
||||
* @example
|
||||
* // `props['data-test-subj'] = undefined;
|
||||
* const getTestId = useTestIdGenerator(props['data-test-subj']);
|
||||
* getTestId('body'); // undefined
|
||||
*/
|
||||
export const useTestIdGenerator = (prefix?: string): ((suffix: string) => string | undefined) => {
|
||||
return useCallback(
|
||||
(suffix: string): string | undefined => {
|
||||
if (prefix) {
|
||||
return `${prefix}-${suffix}`;
|
||||
}
|
||||
},
|
||||
[prefix]
|
||||
);
|
||||
};
|
|
@ -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 './paginated_content';
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import { PaginatedContentProps, PaginatedContent } from './paginated_content';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
|
||||
describe('when using PaginatedContent', () => {
|
||||
interface Foo {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface ItemComponentProps {
|
||||
item: Foo;
|
||||
}
|
||||
|
||||
type ItemComponentType = FC<ItemComponentProps>;
|
||||
|
||||
type PropsForPaginatedContent = PaginatedContentProps<Foo, FC<ItemComponentProps>>;
|
||||
|
||||
const ItemComponent: ItemComponentType = jest.fn((props) => (
|
||||
<div className="foo-item">{'hi'}</div>
|
||||
));
|
||||
|
||||
const getPropsToRenderItem: PropsForPaginatedContent['itemComponentProps'] = jest.fn(
|
||||
(item: Foo) => {
|
||||
return { item };
|
||||
}
|
||||
);
|
||||
|
||||
let render: (
|
||||
additionalProps?: Partial<PropsForPaginatedContent>
|
||||
) => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let onChangeHandler: PropsForPaginatedContent['onChange'];
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
onChangeHandler = jest.fn();
|
||||
|
||||
render = (additionalProps) => {
|
||||
const props: PropsForPaginatedContent = {
|
||||
items: Array.from({ length: 10 }, (v, i) => ({ id: String(i) })),
|
||||
ItemComponent,
|
||||
onChange: onChangeHandler,
|
||||
itemComponentProps: getPropsToRenderItem,
|
||||
pagination: {
|
||||
pageIndex: 0,
|
||||
pageSizeOptions: [5, 10, 20],
|
||||
pageSize: 5,
|
||||
totalItemCount: 10,
|
||||
},
|
||||
'data-test-subj': 'test',
|
||||
...(additionalProps ?? {}),
|
||||
};
|
||||
renderResult = mockedContext.render(<PaginatedContent<Foo, ItemComponentType> {...props} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render items using provided component', () => {
|
||||
render({ itemId: 'id' }); // Using `itemsId` prop just to ensure that branch of code is executed
|
||||
|
||||
expect(renderResult.baseElement.querySelectorAll('.foo-item').length).toBe(10);
|
||||
expect(getPropsToRenderItem).toHaveBeenNthCalledWith(1, { id: '0' });
|
||||
expect(ItemComponent).toHaveBeenNthCalledWith(1, { item: { id: '0' } }, {});
|
||||
expect(renderResult.getByTestId('test-footer')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show default "no items found message" when no data to display', () => {
|
||||
render({ items: [] });
|
||||
|
||||
expect(renderResult.getByText('No items found')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should allow for a custom no items found message to be displayed', () => {
|
||||
render({ items: [], noItemsMessage: 'no Foo found!' });
|
||||
|
||||
expect(renderResult.getByText('no Foo found!')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show error if one is defined (even if `items` is not empty)', () => {
|
||||
render({ error: 'something is wrong with foo' });
|
||||
|
||||
expect(renderResult.getByText('something is wrong with foo')).not.toBeNull();
|
||||
expect(renderResult.baseElement.querySelectorAll('.foo-item').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should show a progress bar if `loading` is set to true', () => {
|
||||
render({ loading: true });
|
||||
|
||||
expect(renderResult.baseElement.querySelector('.euiProgress')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT show a pagination footer if no props are defined for `pagination`', () => {
|
||||
render({ pagination: undefined });
|
||||
|
||||
expect(renderResult.queryByTestId('test-footer')).toBeNull();
|
||||
});
|
||||
|
||||
it('should apply `contentClassName` if one is defined', () => {
|
||||
render({ contentClassName: 'foo-content' });
|
||||
|
||||
expect(renderResult.baseElement.querySelector('.foo-content')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should call onChange when pagination is changed', () => {
|
||||
render();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(renderResult.getByTestId('pagination-button-next'));
|
||||
});
|
||||
|
||||
expect(onChangeHandler).toHaveBeenCalledWith({
|
||||
pageIndex: 1,
|
||||
pageSize: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onChange when page size is changed', () => {
|
||||
render();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(renderResult.getByTestId('tablePaginationPopoverButton'));
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(renderResult.getByTestId('tablePagination-10-rows'));
|
||||
});
|
||||
|
||||
expect(onChangeHandler).toHaveBeenCalledWith({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore items, error, noItemsMessage when `children` is used', () => {
|
||||
render({ children: <div data-test-subj="custom-content">{'children being used here'}</div> });
|
||||
expect(renderResult.getByTestId('custom-content')).not.toBeNull();
|
||||
expect(renderResult.baseElement.querySelectorAll('.foo-item').length).toBe(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
ComponentProps,
|
||||
ComponentType,
|
||||
FunctionComponent,
|
||||
Key,
|
||||
memo,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
CommonProps,
|
||||
EuiEmptyPrompt,
|
||||
EuiIcon,
|
||||
EuiProgress,
|
||||
EuiSpacer,
|
||||
EuiTablePagination,
|
||||
EuiTablePaginationProps,
|
||||
EuiText,
|
||||
Pagination,
|
||||
} from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { v4 as generateUUI } from 'uuid';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type ComponentWithAnyProps = ComponentType<any>;
|
||||
|
||||
export interface PaginatedContentProps<T, C extends ComponentWithAnyProps> extends CommonProps {
|
||||
items: T[];
|
||||
onChange: (changes: { pageIndex: number; pageSize: number }) => void;
|
||||
/**
|
||||
* The React Component that will be used to render the `items`. use `itemComponentProps` below to
|
||||
* define the props that will be given to this component
|
||||
*/
|
||||
ItemComponent: C;
|
||||
/** A callback that will be used to retrieve the props for the `ItemComponent` */
|
||||
itemComponentProps: (item: T) => ComponentProps<C>;
|
||||
/** The item attribute that holds its unique value */
|
||||
itemId?: keyof T;
|
||||
loading?: boolean;
|
||||
pagination?: Pagination;
|
||||
noItemsMessage?: ReactNode;
|
||||
/** Error to be displayed in the component's body area. Used when `items` is empty and `children` is not used */
|
||||
error?: ReactNode;
|
||||
/** Classname applied to the area that holds the content items */
|
||||
contentClassName?: string;
|
||||
/**
|
||||
* Children can be used to define custom content if the default creation of items is not sufficient
|
||||
* to accommodate a use case.
|
||||
*
|
||||
* **IMPORTANT** If defined several input props will be ignored, like `items`, `noItemsMessage`
|
||||
* and `error` among others
|
||||
*/
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
// Using `memo()` on generic typed Functional component is not supported (generic is lost),
|
||||
// Work around below was created based on this discussion:
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/37087#issuecomment-568218789
|
||||
interface TypedGenericComponentMemo {
|
||||
<T, C extends ComponentWithAnyProps>(p: PaginatedContentProps<T, C>): ReactElement<
|
||||
PaginatedContentProps<T, C>,
|
||||
FunctionComponent<PaginatedContentProps<T, C>>
|
||||
>;
|
||||
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
const RootContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
.body {
|
||||
min-height: ${({ theme }) => theme.eui.gutterTypes.gutterExtraLarge};
|
||||
|
||||
&-content {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DefaultNoItemsFound = memo(() => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.paginatedContent.noItemsFoundTitle"
|
||||
defaultMessage="No items found"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
DefaultNoItemsFound.displayName = 'DefaultNoItemsFound';
|
||||
|
||||
const ErrorMessage = memo<{ message: string }>(({ message }) => {
|
||||
return (
|
||||
<EuiText textAlign="center">
|
||||
<EuiSpacer size="m" />
|
||||
<EuiIcon type="minusInCircle" color="danger" /> {message}
|
||||
<EuiSpacer size="m" />
|
||||
</EuiText>
|
||||
);
|
||||
});
|
||||
|
||||
ErrorMessage.displayName = 'ErrorMessage';
|
||||
|
||||
/**
|
||||
* A generic component to display paginated content. Provides "Items per Page" as well as pagination
|
||||
* controls similar to the BasicTable of EUI. The props supported by this component (for the most part)
|
||||
* support those that BasicTable accept.
|
||||
*/
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const PaginatedContent = memo(
|
||||
<T extends object, C extends ComponentWithAnyProps>({
|
||||
items,
|
||||
ItemComponent,
|
||||
itemComponentProps,
|
||||
itemId,
|
||||
onChange,
|
||||
pagination,
|
||||
loading,
|
||||
noItemsMessage,
|
||||
error,
|
||||
contentClassName,
|
||||
'data-test-subj': dataTestSubj,
|
||||
'aria-label': ariaLabel,
|
||||
className,
|
||||
children,
|
||||
}: PaginatedContentProps<T, C>) => {
|
||||
const [itemKeys] = useState<WeakMap<T, string>>(new WeakMap());
|
||||
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const pageCount = useMemo(
|
||||
() => Math.ceil((pagination?.totalItemCount || 1) / (pagination?.pageSize || 1)),
|
||||
[pagination?.pageSize, pagination?.totalItemCount]
|
||||
);
|
||||
|
||||
const handleItemsPerPageChange: EuiTablePaginationProps['onChangeItemsPerPage'] = useCallback(
|
||||
(pageSize) => {
|
||||
onChange({ pageSize, pageIndex: pagination?.pageIndex || 0 });
|
||||
},
|
||||
[onChange, pagination?.pageIndex]
|
||||
);
|
||||
|
||||
const handlePageChange: EuiTablePaginationProps['onChangePage'] = useCallback(
|
||||
(pageIndex) => {
|
||||
onChange({ pageIndex, pageSize: pagination?.pageSize || 10 });
|
||||
},
|
||||
[onChange, pagination?.pageSize]
|
||||
);
|
||||
|
||||
const generatedBodyItemContent = useMemo(() => {
|
||||
if (error) {
|
||||
return 'string' === typeof error ? <ErrorMessage message={error} /> : error;
|
||||
}
|
||||
|
||||
// This casting here is needed in order to avoid the following a TS error (TS2322)
|
||||
// stating that the attributes given to the `ItemComponent` are not assignable to
|
||||
// type 'LibraryManagedAttributes<C, any>'
|
||||
// @see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34553
|
||||
const Item = ItemComponent as ComponentType<ReturnType<typeof itemComponentProps>>;
|
||||
|
||||
if (items.length) {
|
||||
return items.map((item) => {
|
||||
let key: Key;
|
||||
|
||||
if (itemId) {
|
||||
key = (item[itemId] as unknown) as Key;
|
||||
} else {
|
||||
if (itemKeys.has(item)) {
|
||||
key = itemKeys.get(item)!;
|
||||
} else {
|
||||
key = generateUUI();
|
||||
itemKeys.set(item, key);
|
||||
}
|
||||
}
|
||||
|
||||
return <Item {...itemComponentProps(item)} key={key} />;
|
||||
});
|
||||
}
|
||||
|
||||
return noItemsMessage || <DefaultNoItemsFound />;
|
||||
}, [ItemComponent, error, itemComponentProps, itemId, itemKeys, items, noItemsMessage]);
|
||||
|
||||
return (
|
||||
<RootContainer data-test-subj={dataTestSubj} aria-label={ariaLabel} className={className}>
|
||||
{loading && <EuiProgress size="xs" color="primary" />}
|
||||
|
||||
<div className="body" data-test-subj={getTestId('body')}>
|
||||
<EuiSpacer size="l" />
|
||||
<div className={`body-content ${contentClassName}`}>
|
||||
{children ? children : generatedBodyItemContent}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{pagination && (
|
||||
<div data-test-subj={getTestId('footer')}>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiTablePagination
|
||||
activePage={pagination.pageIndex}
|
||||
itemsPerPage={pagination.pageSize}
|
||||
itemsPerPageOptions={pagination.pageSizeOptions}
|
||||
pageCount={pageCount}
|
||||
hidePerPageOptions={pagination.hidePerPageOptions}
|
||||
onChangeItemsPerPage={handleItemsPerPageChange}
|
||||
onChangePage={handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</RootContainer>
|
||||
);
|
||||
}
|
||||
// See type description above to understand why this casting is needed
|
||||
) as TypedGenericComponentMemo;
|
||||
|
||||
PaginatedContent.displayName = 'PaginatedContent';
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { Route, Switch } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { NotFoundPage } from '../../../app/404';
|
||||
import { MANAGEMENT_ROUTING_EVENT_FILTERS_PATH } from '../../common/constants';
|
||||
import { EventFiltersListPage } from './view/event_filters_list_page';
|
||||
|
||||
export const EventFiltersContainer = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={MANAGEMENT_ROUTING_EVENT_FILTERS_PATH} exact component={EventFiltersListPage} />
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 interface EventFiltersListPageUrlSearchParams {
|
||||
page_index: number;
|
||||
page_size: number;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AdministrationListPage } from '../../../components/administration_list_page';
|
||||
|
||||
export const EventFiltersListPage = memo(() => {
|
||||
return (
|
||||
<AdministrationListPage
|
||||
beta={false}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.eventFilters.list.pageTitle"
|
||||
defaultMessage="Event Filters"
|
||||
/>
|
||||
}
|
||||
subtitle={i18n.translate('xpack.securitySolution.eventFilters.aboutInfo', {
|
||||
defaultMessage: 'Something here about Event Filtering....',
|
||||
})}
|
||||
>
|
||||
{/* <PaginatedContent />*/}
|
||||
</AdministrationListPage>
|
||||
);
|
||||
});
|
||||
|
||||
EventFiltersListPage.displayName = 'EventFiltersListPage';
|
|
@ -14,6 +14,7 @@ import { EuiText, EuiEmptyPrompt } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
MANAGEMENT_ROUTING_ENDPOINTS_PATH,
|
||||
MANAGEMENT_ROUTING_EVENT_FILTERS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICIES_PATH,
|
||||
MANAGEMENT_ROUTING_ROOT_PATH,
|
||||
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
|
||||
|
@ -28,14 +29,22 @@ import { GetUrlForApp } from '../../common/components/navigation/types';
|
|||
import { AdministrationRouteSpyState } from '../../common/utils/route/types';
|
||||
import { ADMINISTRATION } from '../../app/home/translations';
|
||||
import { AdministrationSubTab } from '../types';
|
||||
import { ENDPOINTS_TAB, POLICIES_TAB, TRUSTED_APPS_TAB } from '../common/translations';
|
||||
import {
|
||||
ENDPOINTS_TAB,
|
||||
EVENT_FILTERS_TAB,
|
||||
POLICIES_TAB,
|
||||
TRUSTED_APPS_TAB,
|
||||
} from '../common/translations';
|
||||
import { SpyRoute } from '../../common/utils/route/spy_routes';
|
||||
import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled';
|
||||
import { EventFiltersContainer } from './event_filters';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
|
||||
|
||||
const TabNameMappedToI18nKey: Record<AdministrationSubTab, string> = {
|
||||
[AdministrationSubTab.endpoints]: ENDPOINTS_TAB,
|
||||
[AdministrationSubTab.policies]: POLICIES_TAB,
|
||||
[AdministrationSubTab.trustedApps]: TRUSTED_APPS_TAB,
|
||||
[AdministrationSubTab.eventFilters]: EVENT_FILTERS_TAB,
|
||||
};
|
||||
|
||||
export function getBreadcrumbs(
|
||||
|
@ -88,6 +97,7 @@ NoPermissions.displayName = 'NoPermissions';
|
|||
|
||||
export const ManagementContainer = memo(() => {
|
||||
const history = useHistory();
|
||||
const isEventFilteringEnabled = useIsExperimentalFeatureEnabled('eventFilteringEnabled');
|
||||
const { allEnabled: isIngestEnabled } = useIngestEnabledCheck();
|
||||
|
||||
if (!isIngestEnabled) {
|
||||
|
@ -99,6 +109,11 @@ export const ManagementContainer = memo(() => {
|
|||
<Route path={MANAGEMENT_ROUTING_ENDPOINTS_PATH} component={EndpointsContainer} />
|
||||
<Route path={MANAGEMENT_ROUTING_POLICIES_PATH} component={PolicyContainer} />
|
||||
<Route path={MANAGEMENT_ROUTING_TRUSTED_APPS_PATH} component={TrustedAppsContainer} />
|
||||
|
||||
{isEventFilteringEnabled && (
|
||||
<Route path={MANAGEMENT_ROUTING_EVENT_FILTERS_PATH} component={EventFiltersContainer} />
|
||||
)}
|
||||
|
||||
<Route
|
||||
path={MANAGEMENT_ROUTING_ROOT_PATH}
|
||||
exact
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
ENTRY_PROPERTY_TITLES,
|
||||
OPERATOR_TITLE,
|
||||
} from '../../translations';
|
||||
import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
|
||||
|
||||
const ConditionEntryCell = memo<{
|
||||
showLabel: boolean;
|
||||
|
@ -76,9 +77,7 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
|||
onVisited,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const getTestId = useCallback((suffix: string) => dataTestSubj && `${dataTestSubj}-${suffix}`, [
|
||||
dataTestSubj,
|
||||
]);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const fieldOptions = useMemo<Array<EuiSuperSelectOption<string>>>(() => {
|
||||
const getDropdownDisplay = (field: ConditionEntryField) => (
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiHideFor, EuiSpacer } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ConditionEntry, OperatingSystem } from '../../../../../../../common/endpoint/types';
|
||||
import { AndOrBadge } from '../../../../../../common/components/and_or_badge';
|
||||
import { ConditionEntryInput, ConditionEntryInputProps } from '../condition_entry_input';
|
||||
import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
|
||||
|
||||
const ConditionGroupFlexGroup = styled(EuiFlexGroup)`
|
||||
// The positioning of the 'and-badge' is done by using the EuiButton's height and adding on to it
|
||||
|
@ -63,14 +64,8 @@ export const ConditionGroup = memo<ConditionGroupProps>(
|
|||
onVisited,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const getTestId = useCallback(
|
||||
(suffix: string): string | undefined => {
|
||||
if (dataTestSubj) {
|
||||
return `${dataTestSubj}-${suffix}`;
|
||||
}
|
||||
},
|
||||
[dataTestSubj]
|
||||
);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
<ConditionGroupFlexGroup gutterSize="xs" data-test-subj={dataTestSubj}>
|
||||
{entries.length > 1 && (
|
||||
|
|
|
@ -44,6 +44,7 @@ import { ABOUT_TRUSTED_APPS, CREATE_TRUSTED_APP_ERROR } from '../translations';
|
|||
import { defaultNewTrustedApp } from '../../store/builders';
|
||||
import { getTrustedAppsListPath } from '../../../../common/routing';
|
||||
import { useToasts } from '../../../../../common/lib/kibana';
|
||||
import { useTestIdGenerator } from '../../../../components/hooks/use_test_id_generator';
|
||||
|
||||
type CreateTrustedAppFlyoutProps = Omit<EuiFlyoutProps, 'hideCloseButton'>;
|
||||
export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
||||
|
@ -81,14 +82,7 @@ export const CreateTrustedAppFlyout = memo<CreateTrustedAppFlyoutProps>(
|
|||
};
|
||||
}, [isLoadingPolicies, policyList]);
|
||||
|
||||
const getTestId = useCallback(
|
||||
(suffix: string): string | undefined => {
|
||||
if (dataTestSubj) {
|
||||
return `${dataTestSubj}-${suffix}`;
|
||||
}
|
||||
},
|
||||
[dataTestSubj]
|
||||
);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
if (creationInProgress) {
|
||||
|
|
|
@ -42,6 +42,7 @@ import {
|
|||
EffectedPolicySelection,
|
||||
EffectedPolicySelectProps,
|
||||
} from './effected_policy_select';
|
||||
import { useTestIdGenerator } from '../../../../components/hooks/use_test_id_generator';
|
||||
|
||||
const OPERATING_SYSTEMS: readonly OperatingSystem[] = [
|
||||
OperatingSystem.MAC,
|
||||
|
@ -212,14 +213,7 @@ export const CreateTrustedAppForm = memo<CreateTrustedAppFormProps>(
|
|||
>
|
||||
>({});
|
||||
|
||||
const getTestId = useCallback(
|
||||
(suffix: string): string | undefined => {
|
||||
if (dataTestSubj) {
|
||||
return `${dataTestSubj}-${suffix}`;
|
||||
}
|
||||
},
|
||||
[dataTestSubj]
|
||||
);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const notifyOfChange = useCallback(
|
||||
(updatedFormValues: TrustedAppFormState['item']) => {
|
||||
|
|
|
@ -26,6 +26,7 @@ import { getPolicyDetailPath } from '../../../../../common/routing';
|
|||
import { useFormatUrl } from '../../../../../../common/components/link_to';
|
||||
import { SecurityPageName } from '../../../../../../../common/constants';
|
||||
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
|
||||
import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
|
||||
|
||||
const NOOP = () => {};
|
||||
const DEFAULT_LIST_PROPS: EuiSelectableProps['listProps'] = { bordered: true, showIcons: false };
|
||||
|
@ -69,14 +70,7 @@ export const EffectedPolicySelect = memo<EffectedPolicySelectProps>(
|
|||
}) => {
|
||||
const { formatUrl } = useFormatUrl(SecurityPageName.administration);
|
||||
|
||||
const getTestId = useCallback(
|
||||
(suffix): string | undefined => {
|
||||
if (dataTestSubj) {
|
||||
return `${dataTestSubj}-${suffix}`;
|
||||
}
|
||||
},
|
||||
[dataTestSubj]
|
||||
);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const selectableOptions: EffectedPolicyOption[] = useMemo(() => {
|
||||
const isPolicySelected = new Set<string>(selected.map((policy) => policy.id));
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { CommonProps, EuiText, EuiPanel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ConditionGroup, ConditionGroupProps } from '../condition_group';
|
||||
import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
|
||||
|
||||
export type LogicalConditionBuilderProps = CommonProps & ConditionGroupProps;
|
||||
export const LogicalConditionBuilder = memo<LogicalConditionBuilderProps>(
|
||||
|
@ -23,14 +24,8 @@ export const LogicalConditionBuilder = memo<LogicalConditionBuilderProps>(
|
|||
onVisited,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const getTestId = useCallback(
|
||||
(suffix: string): string | undefined => {
|
||||
if (dataTestSubj) {
|
||||
return `${dataTestSubj}-${suffix}`;
|
||||
}
|
||||
},
|
||||
[dataTestSubj]
|
||||
);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
<div data-test-subj={dataTestSubj} className={className}>
|
||||
<div>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,6 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import {
|
||||
createSampleTrustedApp,
|
||||
|
@ -21,13 +20,7 @@ import {
|
|||
} from '../../../test_utils';
|
||||
|
||||
import { TrustedAppsGrid } from '.';
|
||||
import { getMockTheme } from '../../../../../../common/lib/kibana/kibana_react.mock';
|
||||
|
||||
const mockTheme = getMockTheme({
|
||||
eui: {
|
||||
euiSize: '16px',
|
||||
},
|
||||
});
|
||||
import { EuiThemeProvider } from '../../../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => 'mockId',
|
||||
|
@ -38,7 +31,7 @@ const now = 111111;
|
|||
const renderList = (store: ReturnType<typeof createGlobalNoMiddlewareStore>) => {
|
||||
const Wrapper: React.FC = ({ children }) => (
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={mockTheme}>{children}</ThemeProvider>
|
||||
<EuiThemeProvider>{children}</EuiThemeProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
|
|
|
@ -5,18 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, memo, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
EuiTablePagination,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiProgress,
|
||||
EuiIcon,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Pagination } from '../../../state';
|
||||
|
||||
import {
|
||||
|
@ -33,49 +25,26 @@ import {
|
|||
useTrustedAppsStoreActionCallback,
|
||||
} from '../../hooks';
|
||||
|
||||
import { NO_RESULTS_MESSAGE } from '../../translations';
|
||||
|
||||
import { TrustedAppCard, TrustedAppCardProps } from '../trusted_app_card';
|
||||
import { getTrustedAppsListPath } from '../../../../../common/routing';
|
||||
import {
|
||||
PaginatedContent,
|
||||
PaginatedContentProps,
|
||||
} from '../../../../../components/paginated_content';
|
||||
import { TrustedApp } from '../../../../../../../common/endpoint/types';
|
||||
|
||||
export interface PaginationBarProps {
|
||||
pagination: Pagination;
|
||||
onChange: (pagination: { size: number; index: number }) => void;
|
||||
}
|
||||
|
||||
const PaginationBar = ({ pagination, onChange }: PaginationBarProps) => {
|
||||
const pageCount = Math.ceil(pagination.totalItemCount / pagination.pageSize);
|
||||
type TrustedAppCardType = typeof TrustedAppCard;
|
||||
|
||||
useEffect(() => {
|
||||
if (pageCount > 0 && pageCount < pagination.pageIndex + 1) {
|
||||
onChange({ index: pageCount - 1, size: pagination.pageSize });
|
||||
}
|
||||
}, [pageCount, onChange, pagination]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EuiTablePagination
|
||||
activePage={pagination.pageIndex}
|
||||
itemsPerPage={pagination.pageSize}
|
||||
itemsPerPageOptions={pagination.pageSizeOptions}
|
||||
pageCount={pageCount}
|
||||
onChangeItemsPerPage={useCallback((size) => onChange({ index: 0, size }), [onChange])}
|
||||
onChangePage={useCallback((index) => onChange({ index, size: pagination.pageSize }), [
|
||||
pagination.pageSize,
|
||||
onChange,
|
||||
])}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const GridMessage: FC = ({ children }) => (
|
||||
<div className="euiTextAlign--center">
|
||||
<EuiSpacer size="m" />
|
||||
{children}
|
||||
<EuiSpacer size="m" />
|
||||
</div>
|
||||
);
|
||||
const RootWrapper = styled.div`
|
||||
.trusted-app + .trusted-app {
|
||||
margin-top: ${({ theme }) => theme.eui.spacerSizes.l};
|
||||
}
|
||||
`;
|
||||
|
||||
export const TrustedAppsGrid = memo(() => {
|
||||
const history = useHistory();
|
||||
|
@ -103,55 +72,32 @@ export const TrustedAppsGrid = memo(() => {
|
|||
[history, location]
|
||||
);
|
||||
|
||||
const handlePaginationChange = useTrustedAppsNavigateCallback(({ index, size }) => ({
|
||||
page_index: index,
|
||||
page_size: size,
|
||||
const handlePaginationChange: PaginatedContentProps<
|
||||
TrustedApp,
|
||||
TrustedAppCardType
|
||||
>['onChange'] = useTrustedAppsNavigateCallback(({ pageIndex, pageSize }) => ({
|
||||
page_index: pageIndex,
|
||||
page_size: pageSize,
|
||||
}));
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
{isLoading && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiProgress size="xs" color="primary" />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
{error && (
|
||||
<GridMessage>
|
||||
<EuiIcon type="minusInCircle" color="danger" /> {error}
|
||||
</GridMessage>
|
||||
)}
|
||||
{!error && listItems.length === 0 && (
|
||||
<GridMessage>
|
||||
<EuiText size="s">{NO_RESULTS_MESSAGE}</EuiText>
|
||||
</GridMessage>
|
||||
)}
|
||||
{!error && listItems.length > 0 && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGroup direction="column">
|
||||
{listItems.map((item) => (
|
||||
<EuiFlexItem grow={false} key={item.id}>
|
||||
<TrustedAppCard
|
||||
trustedApp={item}
|
||||
onDelete={handleTrustedAppDelete}
|
||||
onEdit={handleTrustedAppEdit}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{!error && pagination.totalItemCount > 0 && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<PaginationBar pagination={pagination} onChange={handlePaginationChange} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<RootWrapper>
|
||||
<PaginatedContent<TrustedApp, TrustedAppCardType>
|
||||
items={listItems as TrustedApp[]}
|
||||
onChange={handlePaginationChange}
|
||||
ItemComponent={TrustedAppCard}
|
||||
itemComponentProps={(ta) => ({
|
||||
trustedApp: ta,
|
||||
onDelete: handleTrustedAppDelete,
|
||||
onEdit: handleTrustedAppEdit,
|
||||
className: 'trusted-app',
|
||||
})}
|
||||
loading={isLoading}
|
||||
itemId="id"
|
||||
error={error}
|
||||
pagination={pagination}
|
||||
/>
|
||||
</RootWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -150,10 +150,6 @@ export const LIST_VIEW_TOGGLE_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const NO_RESULTS_MESSAGE = i18n.translate('xpack.securitySolution.trustedapps.noResults', {
|
||||
defaultMessage: 'No items found',
|
||||
});
|
||||
|
||||
export const CREATE_TRUSTED_APP_ERROR: { [K in string]: string } = {
|
||||
[`duplicatedEntry.${ConditionEntryField.HASH}`]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.error.duplicated.hash',
|
||||
|
|
|
@ -32,6 +32,7 @@ export enum AdministrationSubTab {
|
|||
endpoints = 'endpoints',
|
||||
policies = 'policy',
|
||||
trustedApps = 'trusted_apps',
|
||||
eventFilters = 'event_filters',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20354,7 +20354,6 @@
|
|||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.removeLabel": "エントリを削除",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.group.andOperator": "AND",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.noEntries": "条件が定義されていません",
|
||||
"xpack.securitySolution.trustedapps.noResults": "項目が見つかりません",
|
||||
"xpack.securitySolution.trustedapps.trustedapp.createdAt": "作成日",
|
||||
"xpack.securitySolution.trustedapps.trustedapp.createdBy": "作成者",
|
||||
"xpack.securitySolution.trustedapps.trustedapp.description": "説明",
|
||||
|
|
|
@ -20680,7 +20680,6 @@
|
|||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.removeLabel": "移除条目",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.group.andOperator": "AND",
|
||||
"xpack.securitySolution.trustedapps.logicalConditionBuilder.noEntries": "未定义条件",
|
||||
"xpack.securitySolution.trustedapps.noResults": "找不到项目",
|
||||
"xpack.securitySolution.trustedapps.trustedapp.createdAt": "创建日期",
|
||||
"xpack.securitySolution.trustedapps.trustedapp.createdBy": "创建者",
|
||||
"xpack.securitySolution.trustedapps.trustedapp.description": "描述",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue