[Security Solution][Endpoint] Add event filters summary card to the fleet endpoint tab (#100668) (#100915)
* Shows event filters card on fleet page * Uses aggs instead of while loop to retrieve summary data * Add request and response types in the lists package * Fixes old import * Removes old i18n keys * Removes more old i18n keys * Use consts for exception lists url and endpoint event filter list id * Uses event filters service to retrieve summary data * Fixes addressed pr comments such as changing the route without underscore, adding aggs type, validating response, and more * Uses useMemo instead of useState to memoize object * Add new e2e test for summart endpoint * Handle api errors on event filters and trusted apps summary api calls * Add api error message to the toast * Fix wrong i18n key * Change span tag by react fragment * Uses styled components instead of modify compontent style directly and small improvements on test -> ts * Adds curls script for summary route Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: David Sánchez <davidsansol92@gmail.com>
This commit is contained in:
parent
3c7b809756
commit
499d47a7e6
|
@ -31,6 +31,7 @@ export * from './read_exception_list_item_schema';
|
|||
export * from './read_exception_list_schema';
|
||||
export * from './read_list_item_schema';
|
||||
export * from './read_list_schema';
|
||||
export * from './summary_exception_list_schema';
|
||||
export * from './update_endpoint_list_item_schema';
|
||||
export * from './update_exception_list_item_schema';
|
||||
export * from './update_exception_list_item_validation';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ID, LIST_ID, NAMESPACE_TYPE } from '../../constants/index.mock';
|
||||
|
||||
import { SummaryExceptionListSchema } from '.';
|
||||
|
||||
export const getSummaryExceptionListSchemaMock = (): SummaryExceptionListSchema => ({
|
||||
id: ID,
|
||||
list_id: LIST_ID,
|
||||
namespace_type: NAMESPACE_TYPE,
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
|
||||
|
||||
import { getSummaryExceptionListSchemaMock } from './index.mock';
|
||||
import { SummaryExceptionListSchema, summaryExceptionListSchema } from '.';
|
||||
|
||||
describe('summary_exception_list_schema', () => {
|
||||
test('it should validate a typical exception list request', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "id"', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
delete payload.id;
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "list_id"', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
delete payload.list_id;
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "namespace_type" but default to "single"', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
delete payload.namespace_type;
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getSummaryExceptionListSchemaMock());
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "id", "list_id", "namespace_type" but default "namespace_type" to "single"', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
delete payload.id;
|
||||
delete payload.namespace_type;
|
||||
delete payload.list_id;
|
||||
const output = getSummaryExceptionListSchemaMock();
|
||||
delete output.id;
|
||||
delete output.list_id;
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(output);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "id", "list_id"', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
delete payload.id;
|
||||
delete payload.list_id;
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "id", "namespace_type" but default "namespace_type" to "single"', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
delete payload.id;
|
||||
delete payload.namespace_type;
|
||||
const output = getSummaryExceptionListSchemaMock();
|
||||
delete output.id;
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(output);
|
||||
});
|
||||
|
||||
test('it should accept an undefined for "list_id", "namespace_type" but default "namespace_type" to "single"', () => {
|
||||
const payload = getSummaryExceptionListSchemaMock();
|
||||
delete payload.namespace_type;
|
||||
delete payload.list_id;
|
||||
const output = getSummaryExceptionListSchemaMock();
|
||||
delete output.list_id;
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(output);
|
||||
});
|
||||
|
||||
test('it should not allow an extra key to be sent in', () => {
|
||||
const payload: SummaryExceptionListSchema & {
|
||||
extraKey?: string;
|
||||
} = getSummaryExceptionListSchemaMock();
|
||||
payload.extraKey = 'some new value';
|
||||
const decoded = summaryExceptionListSchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { NamespaceType } from '../../common/default_namespace';
|
||||
import { RequiredKeepUndefined } from '../../common/required_keep_undefined';
|
||||
import { id } from '../../common/id';
|
||||
import { list_id } from '../../common/list_id';
|
||||
import { namespace_type } from '../../common/namespace_type';
|
||||
|
||||
export const summaryExceptionListSchema = t.exact(
|
||||
t.partial({
|
||||
id,
|
||||
list_id,
|
||||
namespace_type, // defaults to 'single' if not set during decode
|
||||
})
|
||||
);
|
||||
|
||||
export type SummaryExceptionListSchema = t.OutputOf<typeof summaryExceptionListSchema>;
|
||||
|
||||
// This type is used after a decode since some things are defaults after a decode.
|
||||
export type SummaryExceptionListSchemaDecoded = Omit<
|
||||
RequiredKeepUndefined<t.TypeOf<typeof summaryExceptionListSchema>>,
|
||||
'namespace_type'
|
||||
> & {
|
||||
namespace_type: NamespaceType;
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExceptionListSummarySchema } from '.';
|
||||
|
||||
export const getListSummaryResponseMock = (): ExceptionListSummarySchema => ({
|
||||
windows: 0,
|
||||
linux: 1,
|
||||
macos: 2,
|
||||
total: 3,
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
|
||||
|
||||
import { getListSummaryResponseMock } from './index.mock';
|
||||
import { ExceptionListSummarySchema, exceptionListSummarySchema } from '.';
|
||||
|
||||
describe('list_summary_schema', () => {
|
||||
test('it should validate a typical list summary response', () => {
|
||||
const payload = getListSummaryResponseMock();
|
||||
const decoded = exceptionListSummarySchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT accept an undefined for "windows"', () => {
|
||||
const payload = getListSummaryResponseMock();
|
||||
// @ts-expect-error
|
||||
delete payload.windows;
|
||||
const decoded = exceptionListSummarySchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "windows"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT accept an undefined for "linux"', () => {
|
||||
const payload = getListSummaryResponseMock();
|
||||
// @ts-expect-error
|
||||
delete payload.linux;
|
||||
const decoded = exceptionListSummarySchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "linux"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT accept an undefined for "macos"', () => {
|
||||
const payload = getListSummaryResponseMock();
|
||||
// @ts-expect-error
|
||||
delete payload.macos;
|
||||
const decoded = exceptionListSummarySchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "macos"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT accept an undefined for "total"', () => {
|
||||
const payload = getListSummaryResponseMock();
|
||||
// @ts-expect-error
|
||||
delete payload.total;
|
||||
const decoded = exceptionListSummarySchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "total"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not allow an extra key to be sent in', () => {
|
||||
const payload: ExceptionListSummarySchema & {
|
||||
extraKey?: string;
|
||||
} = getListSummaryResponseMock();
|
||||
payload.extraKey = 'some new value';
|
||||
const decoded = exceptionListSummarySchema.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PositiveInteger } from '@kbn/securitysolution-io-ts-types';
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const exceptionListSummarySchema = t.exact(
|
||||
t.type({
|
||||
windows: PositiveInteger,
|
||||
linux: PositiveInteger,
|
||||
macos: PositiveInteger,
|
||||
total: PositiveInteger,
|
||||
})
|
||||
);
|
||||
|
||||
export type ExceptionListSummarySchema = t.TypeOf<typeof exceptionListSummarySchema>;
|
|
@ -16,5 +16,6 @@ export * from './found_list_item_schema';
|
|||
export * from './found_list_schema';
|
||||
export * from './list_item_schema';
|
||||
export * from './list_schema';
|
||||
export * from './exception_list_summary_schema';
|
||||
export * from './list_item_index_exist_schema';
|
||||
export * from './search_list_item_schema';
|
||||
|
|
|
@ -36,6 +36,7 @@ export * from './read_list_index_route';
|
|||
export * from './read_list_item_route';
|
||||
export * from './read_list_route';
|
||||
export * from './read_privileges_route';
|
||||
export * from './summary_exception_list_route';
|
||||
export * from './update_endpoint_list_item_route';
|
||||
export * from './update_exception_list_item_route';
|
||||
export * from './update_exception_list_route';
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
readListItemRoute,
|
||||
readListRoute,
|
||||
readPrivilegesRoute,
|
||||
summaryExceptionListRoute,
|
||||
updateEndpointListItemRoute,
|
||||
updateExceptionListItemRoute,
|
||||
updateExceptionListRoute,
|
||||
|
@ -95,4 +96,7 @@ export const initRoutes = (router: ListsPluginRouter, config: ConfigType): void
|
|||
updateEndpointListItemRoute(router);
|
||||
deleteEndpointListItemRoute(router);
|
||||
findEndpointListItemRoute(router);
|
||||
|
||||
// exception list items summary
|
||||
summaryExceptionListRoute(router);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 { validate } from '@kbn/securitysolution-io-ts-utils';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import {
|
||||
SummaryExceptionListSchemaDecoded,
|
||||
exceptionListSummarySchema,
|
||||
summaryExceptionListSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
|
||||
|
||||
import type { ListsPluginRouter } from '../types';
|
||||
|
||||
import {
|
||||
buildRouteValidation,
|
||||
buildSiemResponse,
|
||||
getErrorMessageExceptionList,
|
||||
getExceptionListClient,
|
||||
} from './utils';
|
||||
|
||||
export const summaryExceptionListRoute = (router: ListsPluginRouter): void => {
|
||||
router.get(
|
||||
{
|
||||
options: {
|
||||
tags: ['access:lists-summary'],
|
||||
},
|
||||
path: `${EXCEPTION_LIST_URL}/summary`,
|
||||
validate: {
|
||||
query: buildRouteValidation<
|
||||
typeof summaryExceptionListSchema,
|
||||
SummaryExceptionListSchemaDecoded
|
||||
>(summaryExceptionListSchema),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
const { id, list_id: listId, namespace_type: namespaceType } = request.query;
|
||||
const exceptionLists = getExceptionListClient(context);
|
||||
if (id != null || listId != null) {
|
||||
const exceptionListSummary = await exceptionLists.getExceptionListSummary({
|
||||
id,
|
||||
listId,
|
||||
namespaceType,
|
||||
});
|
||||
if (exceptionListSummary == null) {
|
||||
return siemResponse.error({
|
||||
body: getErrorMessageExceptionList({ id, listId }),
|
||||
statusCode: 404,
|
||||
});
|
||||
} else {
|
||||
const [validated, errors] = validate(exceptionListSummary, exceptionListSummarySchema);
|
||||
if (errors != null) {
|
||||
return response.ok({ body: exceptionListSummary });
|
||||
} else {
|
||||
return response.ok({ body: validated ?? {} });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return siemResponse.error({ body: 'id or list_id required', statusCode: 400 });
|
||||
}
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
25
x-pack/plugins/lists/server/scripts/summary_exception_list.sh
Executable file
25
x-pack/plugins/lists/server/scripts/summary_exception_list.sh
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
set -e
|
||||
./check_env_variables.sh
|
||||
|
||||
|
||||
LIST_ID=${1:-endpoint_list}
|
||||
NAMESPACE_TYPE=${2-agnostic}
|
||||
|
||||
# First, post a exception list and two list items for the example to work
|
||||
# ./post_exception_list.sh ./exception_lists/new/exception_list_agnostic.json
|
||||
# ./post_exception_list_item.sh ./exception_lists/new/exception_list_item_agnostic.json
|
||||
|
||||
# Retrieve exception list stats by os
|
||||
# Example: ./summary_exception_list.sh endpoint_list agnostic
|
||||
curl -s -k \
|
||||
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
|
||||
-X GET "${KIBANA_URL}${SPACE_URL}/api/exception_lists/summary?list_id=${LIST_ID}&namespace_type=${NAMESPACE_TYPE}" | jq .
|
|
@ -9,6 +9,7 @@ import { SavedObjectsClientContract } from 'kibana/server';
|
|||
import type {
|
||||
ExceptionListItemSchema,
|
||||
ExceptionListSchema,
|
||||
ExceptionListSummarySchema,
|
||||
FoundExceptionListItemSchema,
|
||||
FoundExceptionListSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
@ -31,11 +32,13 @@ import {
|
|||
GetEndpointListItemOptions,
|
||||
GetExceptionListItemOptions,
|
||||
GetExceptionListOptions,
|
||||
GetExceptionListSummaryOptions,
|
||||
UpdateEndpointListItemOptions,
|
||||
UpdateExceptionListItemOptions,
|
||||
UpdateExceptionListOptions,
|
||||
} from './exception_list_client_types';
|
||||
import { getExceptionList } from './get_exception_list';
|
||||
import { getExceptionListSummary } from './get_exception_list_summary';
|
||||
import { createExceptionList } from './create_exception_list';
|
||||
import { getExceptionListItem } from './get_exception_list_item';
|
||||
import { createExceptionListItem } from './create_exception_list_item';
|
||||
|
@ -72,6 +75,15 @@ export class ExceptionListClient {
|
|||
return getExceptionList({ id, listId, namespaceType, savedObjectsClient });
|
||||
};
|
||||
|
||||
public getExceptionListSummary = async ({
|
||||
listId,
|
||||
id,
|
||||
namespaceType,
|
||||
}: GetExceptionListSummaryOptions): Promise<ExceptionListSummarySchema | null> => {
|
||||
const { savedObjectsClient } = this;
|
||||
return getExceptionListSummary({ id, listId, namespaceType, savedObjectsClient });
|
||||
};
|
||||
|
||||
public getExceptionListItem = async ({
|
||||
itemId,
|
||||
id,
|
||||
|
|
|
@ -56,6 +56,12 @@ export interface GetExceptionListOptions {
|
|||
namespaceType: NamespaceType;
|
||||
}
|
||||
|
||||
export interface GetExceptionListSummaryOptions {
|
||||
listId: ListIdOrUndefined;
|
||||
id: IdOrUndefined;
|
||||
namespaceType: NamespaceType;
|
||||
}
|
||||
|
||||
export interface CreateExceptionListOptions {
|
||||
listId: ListId;
|
||||
namespaceType: NamespaceType;
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 type {
|
||||
ExceptionListSummarySchema,
|
||||
IdOrUndefined,
|
||||
ListIdOrUndefined,
|
||||
NamespaceType,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { getSavedObjectType } from '@kbn/securitysolution-list-utils';
|
||||
|
||||
import {
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsErrorHelpers,
|
||||
} from '../../../../../../src/core/server/';
|
||||
import { ExceptionListSoSchema } from '../../schemas/saved_objects';
|
||||
|
||||
interface GetExceptionListSummaryOptions {
|
||||
id: IdOrUndefined;
|
||||
listId: ListIdOrUndefined;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
namespaceType: NamespaceType;
|
||||
}
|
||||
|
||||
interface ByOsAggBucketType {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
}
|
||||
interface ByOsAggType {
|
||||
by_os: {
|
||||
buckets: ByOsAggBucketType[];
|
||||
};
|
||||
}
|
||||
|
||||
export const getExceptionListSummary = async ({
|
||||
id,
|
||||
listId,
|
||||
savedObjectsClient,
|
||||
namespaceType,
|
||||
}: GetExceptionListSummaryOptions): Promise<ExceptionListSummarySchema | null> => {
|
||||
const savedObjectType = getSavedObjectType({ namespaceType });
|
||||
let finalListId: string = listId ?? '';
|
||||
|
||||
// If id and no listId, get the list by id to use the list_id for the find below
|
||||
if (listId === null && id != null) {
|
||||
try {
|
||||
const savedObject = await savedObjectsClient.get<ExceptionListSoSchema>(savedObjectType, id);
|
||||
finalListId = savedObject.attributes.list_id;
|
||||
} catch (err) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
|
||||
return null;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const savedObject = await savedObjectsClient.find<ExceptionListSoSchema, ByOsAggType>({
|
||||
aggs: {
|
||||
by_os: {
|
||||
terms: {
|
||||
field: `${savedObjectType}.attributes.os_types`,
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: `${savedObjectType}.attributes.list_type: item`,
|
||||
perPage: 0,
|
||||
search: finalListId,
|
||||
searchFields: ['list_id'],
|
||||
sortField: 'tie_breaker_id',
|
||||
sortOrder: 'desc',
|
||||
type: savedObjectType,
|
||||
});
|
||||
|
||||
if (!savedObject.aggregations) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const summary: ExceptionListSummarySchema = savedObject.aggregations.by_os.buckets.reduce(
|
||||
(acc, item: ByOsAggBucketType) => ({
|
||||
...acc,
|
||||
[item.key]: item.doc_count,
|
||||
total: acc.total + item.doc_count,
|
||||
}),
|
||||
{ linux: 0, macos: 0, total: 0, windows: 0 }
|
||||
);
|
||||
|
||||
return summary;
|
||||
};
|
|
@ -15,5 +15,6 @@ export * from './find_exception_list_item';
|
|||
export * from './find_exception_list_items';
|
||||
export * from './get_exception_list';
|
||||
export * from './get_exception_list_item';
|
||||
export * from './get_exception_list_summary';
|
||||
export * from './update_exception_list';
|
||||
export * from './update_exception_list_item';
|
||||
|
|
|
@ -1095,3 +1095,13 @@ export interface GetAgentSummaryResponse {
|
|||
versions_count: { [key: string]: number };
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API response for retrieving exception summary
|
||||
*/
|
||||
export interface GetExceptionSummaryResponse {
|
||||
total: number;
|
||||
windows: number;
|
||||
macos: number;
|
||||
linux: number;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import type {
|
|||
ExceptionListItemSchema,
|
||||
CreateExceptionListItemSchema,
|
||||
UpdateExceptionListItemSchema,
|
||||
ExceptionListSummarySchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants';
|
||||
|
||||
|
@ -102,4 +103,16 @@ export class EventFiltersHttpService implements EventFiltersService {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getSummary(): Promise<ExceptionListSummarySchema> {
|
||||
return (await this.httpWrapper()).get<ExceptionListSummarySchema>(
|
||||
`${EXCEPTION_LIST_URL}/summary`,
|
||||
{
|
||||
query: {
|
||||
list_id: ENDPOINT_EVENT_FILTERS_LIST_ID,
|
||||
namespace_type: 'agnostic',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ const createEventFiltersServiceMock = (): jest.Mocked<EventFiltersService> => ({
|
|||
getOne: jest.fn(),
|
||||
updateOne: jest.fn(),
|
||||
deleteOne: jest.fn(),
|
||||
getSummary: jest.fn(),
|
||||
});
|
||||
|
||||
const createStoreSetup = (eventFiltersService: EventFiltersService) => {
|
||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
|||
CreateExceptionListItemSchema,
|
||||
ExceptionListItemSchema,
|
||||
UpdateExceptionListItemSchema,
|
||||
ExceptionListSummarySchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { AsyncResourceState } from '../../state/async_resource_state';
|
||||
import { Immutable } from '../../../../common/endpoint/types';
|
||||
|
@ -49,6 +50,7 @@ export interface EventFiltersService {
|
|||
getOne(id: string): Promise<ExceptionListItemSchema>;
|
||||
updateOne(exception: Immutable<UpdateExceptionListItemSchema>): Promise<ExceptionListItemSchema>;
|
||||
deleteOne(id: string): Promise<ExceptionListItemSchema>;
|
||||
getSummary(): Promise<ExceptionListSummarySchema>;
|
||||
}
|
||||
|
||||
export interface EventFiltersListPageData {
|
||||
|
|
|
@ -6,56 +6,45 @@
|
|||
*/
|
||||
|
||||
import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import React, { FC, memo, useEffect, useState } from 'react';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import React, { FC, memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { TrustedAppsHttpService } from '../../../../../trusted_apps/service';
|
||||
import { GetTrustedAppsSummaryResponse } from '../../../../../../../../common/endpoint/types';
|
||||
import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types';
|
||||
|
||||
const SUMMARY_KEYS: Readonly<Array<keyof GetTrustedAppsSummaryResponse>> = [
|
||||
const SUMMARY_KEYS: Readonly<Array<keyof GetExceptionSummaryResponse>> = [
|
||||
'windows',
|
||||
'macos',
|
||||
'linux',
|
||||
'total',
|
||||
];
|
||||
|
||||
const SUMMARY_LABELS: Readonly<{ [key in keyof GetTrustedAppsSummaryResponse]: string }> = {
|
||||
const SUMMARY_LABELS: Readonly<{ [key in keyof GetExceptionSummaryResponse]: string }> = {
|
||||
windows: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.windows',
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.windows',
|
||||
{ defaultMessage: 'Windows' }
|
||||
),
|
||||
linux: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.linux',
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.linux',
|
||||
{ defaultMessage: 'Linux' }
|
||||
),
|
||||
macos: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.macos',
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.macos',
|
||||
{ defaultMessage: 'Mac' }
|
||||
),
|
||||
total: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.total',
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.exceptionItemsSummary.total',
|
||||
{ defaultMessage: 'Total' }
|
||||
),
|
||||
};
|
||||
|
||||
const CSS_BOLD: Readonly<React.CSSProperties> = { fontWeight: 'bold' };
|
||||
|
||||
export const TrustedAppItemsSummary = memo(() => {
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana<CoreStart>();
|
||||
const [stats, setStats] = useState<GetTrustedAppsSummaryResponse | undefined>();
|
||||
const [trustedAppsApi] = useState(() => new TrustedAppsHttpService(http));
|
||||
|
||||
useEffect(() => {
|
||||
trustedAppsApi.getTrustedAppsSummary().then((response) => {
|
||||
setStats(response);
|
||||
});
|
||||
}, [trustedAppsApi]);
|
||||
interface ExceptionItemsSummaryProps {
|
||||
stats: GetExceptionSummaryResponse | undefined;
|
||||
}
|
||||
|
||||
export const ExceptionItemsSummary = memo<ExceptionItemsSummaryProps>(({ stats }) => {
|
||||
return (
|
||||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceAround">
|
||||
{SUMMARY_KEYS.map((stat) => {
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
|
@ -73,18 +62,13 @@ export const TrustedAppItemsSummary = memo(() => {
|
|||
);
|
||||
});
|
||||
|
||||
TrustedAppItemsSummary.displayName = 'TrustedAppItemsSummary';
|
||||
ExceptionItemsSummary.displayName = 'ExceptionItemsSummary';
|
||||
|
||||
const SummaryStat: FC<{ value: number; color?: EuiBadgeProps['color'] }> = memo(
|
||||
({ children, value, color, ...commonProps }) => {
|
||||
return (
|
||||
<EuiText className="eui-displayInlineBlock" size="s">
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
justifyContent="center"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiFlexGroup justifyContent="center" direction="row" alignItems="center">
|
||||
<EuiFlexItem grow={false} style={color === 'primary' ? CSS_BOLD : undefined}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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, useMemo, useState, useEffect } from 'react';
|
||||
import { ApplicationStart, CoreStart } from 'kibana/public';
|
||||
import { EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
PackageCustomExtensionComponentProps,
|
||||
pagePathGetters,
|
||||
} from '../../../../../../../../../fleet/public';
|
||||
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { getEventFiltersListPath } from '../../../../../../common/routing';
|
||||
import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types';
|
||||
import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
|
||||
import { MANAGEMENT_APP_ID } from '../../../../../../common/constants';
|
||||
import { useToasts } from '../../../../../../../common/lib/kibana';
|
||||
import { LinkWithIcon } from './link_with_icon';
|
||||
import { ExceptionItemsSummary } from './exception_items_summary';
|
||||
import { EventFiltersHttpService } from '../../../../../event_filters/service';
|
||||
import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components';
|
||||
|
||||
export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>(({ pkgkey }) => {
|
||||
const {
|
||||
services: {
|
||||
application: { getUrlForApp },
|
||||
http,
|
||||
},
|
||||
} = useKibana<CoreStart & { application: ApplicationStart }>();
|
||||
const toasts = useToasts();
|
||||
const [stats, setStats] = useState<GetExceptionSummaryResponse | undefined>();
|
||||
const eventFiltersListUrlPath = getEventFiltersListPath();
|
||||
const eventFiltersApi = useMemo(() => new EventFiltersHttpService(http), [http]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const summary = await eventFiltersApi.getSummary();
|
||||
setStats(summary);
|
||||
} catch (error) {
|
||||
toasts.addDanger(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.eventFiltersSummaryError',
|
||||
{
|
||||
defaultMessage: 'There was an error trying to fetch event filters stats: "{error}"',
|
||||
values: { error },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
fetchStats();
|
||||
}, [eventFiltersApi, toasts]);
|
||||
|
||||
const eventFiltersRouteState = useMemo(() => {
|
||||
const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details_custom({ pkgkey })}`;
|
||||
return {
|
||||
backButtonLabel: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel',
|
||||
{ defaultMessage: 'Back to Endpoint Integration' }
|
||||
),
|
||||
onBackButtonNavigateTo: [
|
||||
FLEET_PLUGIN_ID,
|
||||
{
|
||||
path: fleetPackageCustomUrlPath,
|
||||
},
|
||||
],
|
||||
backButtonUrl: getUrlForApp(FLEET_PLUGIN_ID, {
|
||||
path: fleetPackageCustomUrlPath,
|
||||
}),
|
||||
};
|
||||
}, [getUrlForApp, pkgkey]);
|
||||
|
||||
return (
|
||||
<EuiPanel paddingSize="l">
|
||||
<StyledEuiFlexGridGroup alignItems="baseline" justifyContent="center">
|
||||
<StyledEuiFlexGridItem gridArea="title" alignItems="flex-start">
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.fleetCustomExtension.eventFiltersLabel"
|
||||
defaultMessage="Event Filters"
|
||||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridArea="summary">
|
||||
<ExceptionItemsSummary stats={stats} />
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridArea="link" alignItems="flex-end">
|
||||
<>
|
||||
<LinkWithIcon
|
||||
appId={MANAGEMENT_APP_ID}
|
||||
href={getUrlForApp(MANAGEMENT_APP_ID, { path: eventFiltersListUrlPath })}
|
||||
appPath={eventFiltersListUrlPath}
|
||||
appState={eventFiltersRouteState}
|
||||
data-test-subj="linkToEventFilters"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.fleetCustomExtension.manageEventFiltersLinkLabel"
|
||||
defaultMessage="Manage event filters"
|
||||
/>
|
||||
</LinkWithIcon>
|
||||
</>
|
||||
</StyledEuiFlexGridItem>
|
||||
</StyledEuiFlexGridGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
});
|
||||
|
||||
FleetEventFiltersCard.displayName = 'FleetEventFiltersCard';
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { ApplicationStart } from 'kibana/public';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
|
||||
import React, { memo, useMemo, useState, useEffect } from 'react';
|
||||
import { ApplicationStart, CoreStart } from 'kibana/public';
|
||||
import { EuiPanel, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
|
@ -16,19 +16,48 @@ import {
|
|||
} from '../../../../../../../../../fleet/public';
|
||||
import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { getTrustedAppsListPath } from '../../../../../../common/routing';
|
||||
import { TrustedAppsListPageRouteState } from '../../../../../../../../common/endpoint/types';
|
||||
import {
|
||||
TrustedAppsListPageRouteState,
|
||||
GetExceptionSummaryResponse,
|
||||
} from '../../../../../../../../common/endpoint/types';
|
||||
import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common';
|
||||
import { MANAGEMENT_APP_ID } from '../../../../../../common/constants';
|
||||
import { useToasts } from '../../../../../../../common/lib/kibana';
|
||||
import { LinkWithIcon } from './link_with_icon';
|
||||
import { TrustedAppItemsSummary } from './trusted_app_items_summary';
|
||||
import { ExceptionItemsSummary } from './exception_items_summary';
|
||||
import { TrustedAppsHttpService } from '../../../../../trusted_apps/service';
|
||||
import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components';
|
||||
|
||||
export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>(({ pkgkey }) => {
|
||||
const {
|
||||
services: {
|
||||
application: { getUrlForApp },
|
||||
http,
|
||||
},
|
||||
} = useKibana<{ application: ApplicationStart }>();
|
||||
} = useKibana<CoreStart & { application: ApplicationStart }>();
|
||||
const toasts = useToasts();
|
||||
const [stats, setStats] = useState<GetExceptionSummaryResponse | undefined>();
|
||||
const trustedAppsApi = useMemo(() => new TrustedAppsHttpService(http), [http]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const response = await trustedAppsApi.getTrustedAppsSummary();
|
||||
setStats(response);
|
||||
} catch (error) {
|
||||
toasts.addDanger(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsSummaryError',
|
||||
{
|
||||
defaultMessage: 'There was an error trying to fetch trusted apps stats: "{error}"',
|
||||
values: { error },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
fetchStats();
|
||||
}, [toasts, trustedAppsApi]);
|
||||
const trustedAppsListUrlPath = getTrustedAppsListPath();
|
||||
|
||||
const trustedAppRouteState = useMemo<TrustedAppsListPageRouteState>(() => {
|
||||
|
@ -52,8 +81,8 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>((
|
|||
|
||||
return (
|
||||
<EuiPanel paddingSize="l">
|
||||
<EuiFlexGroup alignItems="baseline">
|
||||
<EuiFlexItem>
|
||||
<StyledEuiFlexGridGroup alignItems="baseline" justifyContent="center">
|
||||
<StyledEuiFlexGridItem gridArea="title" alignItems="flex-start">
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
|
@ -62,12 +91,12 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>((
|
|||
/>
|
||||
</h4>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TrustedAppItemsSummary />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<span>
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridArea="summary">
|
||||
<ExceptionItemsSummary stats={stats} />
|
||||
</StyledEuiFlexGridItem>
|
||||
<StyledEuiFlexGridItem gridArea="link" alignItems="flex-end">
|
||||
<>
|
||||
<LinkWithIcon
|
||||
appId={MANAGEMENT_APP_ID}
|
||||
href={getUrlForApp(MANAGEMENT_APP_ID, { path: trustedAppsListUrlPath })}
|
||||
|
@ -80,9 +109,9 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>((
|
|||
defaultMessage="Manage trusted applications"
|
||||
/>
|
||||
</LinkWithIcon>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
</StyledEuiFlexGridItem>
|
||||
</StyledEuiFlexGridGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 styled from 'styled-components';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
export const StyledEuiFlexGridGroup = styled(EuiFlexGroup)`
|
||||
display: grid;
|
||||
grid-template-columns: 25% 45% 30%;
|
||||
grid-template-areas: 'title summary link';
|
||||
`;
|
||||
|
||||
export const StyledEuiFlexGridItem = styled(EuiFlexItem)<{
|
||||
gridArea: string;
|
||||
alignItems?: string;
|
||||
}>`
|
||||
grid-area: ${({ gridArea }) => gridArea};
|
||||
align-items: ${({ alignItems }) => alignItems ?? 'center'};
|
||||
margin: 0px;
|
||||
padding: 12px;
|
||||
`;
|
|
@ -5,15 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React, { memo } from 'react';
|
||||
import { PackageCustomExtensionComponentProps } from '../../../../../../../../fleet/public';
|
||||
import { FleetTrustedAppsCard } from './components/fleet_trusted_apps_card';
|
||||
import { FleetEventFiltersCard } from './components/fleet_event_filters_card';
|
||||
|
||||
export const EndpointPackageCustomExtension = memo<PackageCustomExtensionComponentProps>(
|
||||
(props) => {
|
||||
return (
|
||||
<div data-test-subj="fleetEndpointPackageCustomContent">
|
||||
<FleetTrustedAppsCard {...props} />
|
||||
<EuiSpacer />
|
||||
<FleetEventFiltersCard {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20655,10 +20655,6 @@
|
|||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失敗} other {不明}}",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel": "エンドポイント統合に戻る",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel": "信頼できるアプリケーションを管理",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.linux": "Linux",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.macos": "Mac",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.total": "合計",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.windows": "Windows",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsLabel": "信頼できるアプリケーション",
|
||||
"xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration": "推奨のデフォルト値で統合が保存されます。後からこれを変更するには、エージェントポリシー内で Endpoint Security 統合を編集します。",
|
||||
"xpack.securitySolution.endpoint.ingestToastMessage": "Fleetが設定中に失敗しました。",
|
||||
|
|
|
@ -20959,10 +20959,6 @@
|
|||
"xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失败} other {未知}}",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel": "返回至终端集成",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.manageTrustedAppLinkLabel": "管理受信任的应用程序",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.linux": "Linux",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.macos": "Mac",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.total": "合计",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppItemsSummary.windows": "Windows",
|
||||
"xpack.securitySolution.endpoint.fleetCustomExtension.trustedAppsLabel": "受信任的应用程序",
|
||||
"xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration": "我们将使用建议的默认值保存您的集成。稍后,您可以通过在代理策略中编辑 Endpoint Security 集成对其进行更改。",
|
||||
"xpack.securitySolution.endpoint.ingestToastMessage": "Fleet 在设置期间失败。",
|
||||
|
|
|
@ -35,5 +35,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
|
|||
loadTestFile(require.resolve('./find_exception_lists'));
|
||||
loadTestFile(require.resolve('./find_exception_list_items'));
|
||||
loadTestFile(require.resolve('./read_list_privileges'));
|
||||
loadTestFile(require.resolve('./summary_exception_lists'));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
|
||||
import { ExceptionListSummarySchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { EXCEPTION_LIST_URL, EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants';
|
||||
import { LIST_ID } from '../../../../plugins/lists/common/constants.mock';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { getCreateExceptionListMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_schema.mock';
|
||||
import { getCreateExceptionListItemMinimalSchemaMock } from '../../../../plugins/lists/common/schemas/request/create_exception_list_item_schema.mock';
|
||||
import { createListsIndex, deleteListsIndex, deleteAllExceptions } from '../../utils';
|
||||
|
||||
interface SummaryResponseType {
|
||||
body: ExceptionListSummarySchema;
|
||||
}
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
const supertest = getService('supertest');
|
||||
const es = getService('es');
|
||||
|
||||
describe('summary_exception_lists', () => {
|
||||
describe('summary exception lists', () => {
|
||||
beforeEach(async () => {
|
||||
await createListsIndex(supertest);
|
||||
});
|
||||
afterEach(async () => {
|
||||
await deleteListsIndex(supertest);
|
||||
await deleteAllExceptions(es);
|
||||
});
|
||||
|
||||
it('should give a validation error if the list_id and the id are not supplied', async () => {
|
||||
const { body } = await supertest
|
||||
.get(`${EXCEPTION_LIST_URL}/summary`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(400);
|
||||
|
||||
expect(body).to.eql({
|
||||
message: 'id or list_id required',
|
||||
status_code: 400,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return init summary when there are no items created', async () => {
|
||||
const { body }: SummaryResponseType = await supertest
|
||||
.get(`${EXCEPTION_LIST_URL}/summary?list_id=${LIST_ID}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
const expected: ExceptionListSummarySchema = {
|
||||
linux: 0,
|
||||
macos: 0,
|
||||
total: 0,
|
||||
windows: 0,
|
||||
};
|
||||
expect(body).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should return right summary when there are items created', async () => {
|
||||
await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
const item = getCreateExceptionListItemMinimalSchemaMock();
|
||||
|
||||
for (const os of ['windows', 'linux', 'macos']) {
|
||||
await supertest
|
||||
.post(EXCEPTION_LIST_ITEM_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ...item, os_types: [os], item_id: `${item.item_id}-${os}` })
|
||||
.expect(200);
|
||||
}
|
||||
|
||||
const { body }: SummaryResponseType = await supertest
|
||||
.get(`${EXCEPTION_LIST_URL}/summary?list_id=${LIST_ID}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
const expected: ExceptionListSummarySchema = {
|
||||
linux: 1,
|
||||
macos: 1,
|
||||
total: 3,
|
||||
windows: 1,
|
||||
};
|
||||
expect(body).to.eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Reference in a new issue