From 533c611d37d3aa5540616bb22670563ea2e6ed22 Mon Sep 17 00:00:00 2001 From: bryan Date: Thu, 28 Jan 2021 23:22:31 -0800 Subject: [PATCH 1/5] group picking kinda works, clean up to come --- .../osquery/public/agents/agents_table.tsx | 208 +++++++++++++++--- 1 file changed, 181 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index e41b74c672e9b..47d9cd4002e22 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -6,12 +6,21 @@ */ import { find } from 'lodash/fp'; -import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import React, { Fragment, useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { EuiBasicTable, EuiBasicTableColumn, EuiBasicTableProps, EuiTableSelectionType, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiOverlayMask, + EuiSelectable, + EuiButton, + EuiButtonEmpty, EuiHealth, } from '@elastic/eui'; @@ -32,6 +41,10 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh const [selectedItems, setSelectedItems] = useState([]); const tableRef = useRef>(null); + const [isModalVisible, setIsModalVisible] = useState(false); + const closeModal = useCallback(() => setIsModalVisible(false), [setIsModalVisible]); + const showModal = useCallback(() => setIsModalVisible(true), [setIsModalVisible]); + const onTableChange: EuiBasicTableProps['onChange'] = useCallback( ({ page = {}, sort = {} }) => { const { index: newPageIndex, size: newPageSize } = page; @@ -46,18 +59,8 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh [] ); - const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback( - (newSelectedItems) => { - setSelectedItems(newSelectedItems); - - if (onChange) { - // @ts-expect-error update types - onChange(newSelectedItems.map((item) => item._id)); - } - }, - [onChange] - ); - + // const GROUP_KEY = 'local_metadata.os.family' + const GROUP_KEY = 'local_metadata.host.name'; const renderStatus = (online: string) => { const color = online ? 'success' : 'danger'; const label = online ? 'Online' : 'Offline'; @@ -71,6 +74,60 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh sortField, }); + // TODO: abstract this to allow for faceting on other dimensions + const [platforms, setPlatforms] = useState(Object.create(null)); + useEffect(() => { + setPlatforms(generateGroupSets(GROUP_KEY, agents)); + }, [agents]); + function generateGroupSets(attributePath: string, groupAgents: Agent[]) { + const path = attributePath.split('.'); + return groupAgents.reduce((acc, agent) => { + let groupKey = agent; + for (const pathFrag of path) { + if (!groupKey) { + // XXX: can't find the key path on the agent object + return acc; + } + groupKey = groupKey[pathFrag]; + } + if (!acc[groupKey]) { + acc[groupKey] = [agent]; + } else { + acc[groupKey].push(agent); + } + return acc; + }, Object.create(null)); + } + + const [platformOptions, setPlatformOptions] = useState([]); + useEffect(() => { + const newOptions = Object.keys(platforms).map((label) => ({ label })); + setPlatformOptions(newOptions); + }, [platforms]); + + const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback( + (newSelectedItems) => { + setSelectedItems(newSelectedItems); + if (newSelectedItems.length) { + const newGroupState = generateGroupSets(GROUP_KEY, newSelectedItems); + for (const el of platformOptions) { + if (newGroupState[el.label]?.length === platforms[el.label]?.length) { + el.checked = 'on'; + } else { + el.checked = undefined; + } + } + } else { + for (const el of platformOptions) { + el.checked = undefined; + } + } + // @ts-expect-error + onChange(newSelectedItems.map((item) => item._id)); + }, + [onChange, platforms, platformOptions] + ); + const columns: Array> = useMemo( () => [ { @@ -79,6 +136,12 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh sortable: true, truncateText: true, }, + { + field: 'local_metadata.os.family', + name: 'platform', + sortable: true, + truncateText: true, + }, { field: 'local_metadata.host.name', name: 'hostname', @@ -94,6 +157,12 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh ], [] ); + const searchProps = useMemo( + () => ({ + 'data-test-subj': 'selectableSearchHere', + }), + [] + ); const pagination = useMemo( () => ({ @@ -142,21 +211,106 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh // @ts-expect-error update types }, [selectedAgents, data.agents, selectedItems.length]); + const onGroupChange = useCallback( + (newOptions) => { + const currentSelectedSet = new Set(selectedItems); + for (let i = 0; i < platformOptions.length; ++i) { + const newOp = newOptions[i]; + const oldOp = platformOptions[i]; + if (newOp.checked !== oldOp.checked) { + const newAgents = platforms[newOp.label]; + const newAgentSet = new Set(newAgents); + if (newOp.checked === 'on') { + const agentDiff = newAgents.filter((a) => !currentSelectedSet.has(a)); + selectedItems.push(...agentDiff); + tableRef.current.setSelection(selectedItems); + } else { + const newSelection = selectedItems.filter((a) => !newAgentSet.has(a)); + tableRef.current.setSelection(newSelection); + } + break; + } + } + setPlatformOptions(newOptions); + }, + [selectedItems, platformOptions, platforms] + ); + + // useEffect(() => { + // if (selectedAgents && agents && selectedItems.length !== selectedAgents.length) { + // tableRef?.current?.setSelection( + // // @ts-expect-error + // selectedAgents.map((agentId) => find({ _id: agentId }, agents)) + // ); + // } + // }, [selectedAgents, agents, selectedItems.length]); + + let modal; + + if (isModalVisible) { + modal = ( + + + + Modal title + + + + + {(list, search) => ( + + {search} + {list} + + )} + + + ref={tableRef} + // @ts-expect-error update types + // eslint-disable-next-line react-perf/jsx-no-new-array-as-prop + items={data.agents ?? []} + itemId="_id" + columns={columns} + pagination={pagination} + sorting={sorting} + isSelectable={true} + selection={selection} + onChange={onTableChange} + rowHeader="firstName" + /> + + + + Cancel + + + Save + + + + + ); + } + + let buttonText; + const numAgents = selectedAgents.length; + if (numAgents > 0) { + buttonText = `${numAgents} Agent${numAgents > 1 ? 's' : ''} Selected`; + } else { + buttonText = 'Select Agents'; + } + return ( - - ref={tableRef} - // @ts-expect-error update types - // eslint-disable-next-line react-perf/jsx-no-new-array-as-prop - items={data.agents ?? []} - itemId="_id" - columns={columns} - pagination={pagination} - sorting={sorting} - isSelectable={true} - selection={selection} - onChange={onTableChange} - rowHeader="firstName" - /> +
+ {buttonText} + {modal} +
); }; From 933e9fbe0485e8d74eb9083bb4e9a972685caece Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 3 Feb 2021 22:18:56 -0800 Subject: [PATCH 2/5] updated to use groups --- .../osquery/public/agents/agents_table.tsx | 190 +++++++++--------- .../osquery/public/agents/use_agent_groups.ts | 21 ++ 2 files changed, 112 insertions(+), 99 deletions(-) create mode 100644 x-pack/plugins/osquery/public/agents/use_agent_groups.ts diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 47d9cd4002e22..2b77fe332f300 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -25,6 +25,7 @@ import { } from '@elastic/eui'; import { useAllAgents } from './use_all_agents'; +import { useAgentGroups, ALL_AGENTS_GROUP_KEY } from './use_agent_groups'; import { Direction } from '../../common/search_strategy'; import { Agent } from '../../common/shared_imports'; @@ -59,73 +60,66 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh [] ); - // const GROUP_KEY = 'local_metadata.os.family' - const GROUP_KEY = 'local_metadata.host.name'; const renderStatus = (online: string) => { const color = online ? 'success' : 'danger'; const label = online ? 'Online' : 'Offline'; return {label}; }; - const { data = {} } = useAllAgents({ - activePage: pageIndex, - limit: pageSize, - direction: sortDirection, - sortField, - }); + const agentGroups = useAgentGroups(); + const [selectedGroups, setSelectedGroups] = useState([]); + const [allAgentsSelected, setAllAgentsSelected] = useState(false); + + const [groupOptions, setGroupOptions] = useState([]); - // TODO: abstract this to allow for faceting on other dimensions - const [platforms, setPlatforms] = useState(Object.create(null)); useEffect(() => { - setPlatforms(generateGroupSets(GROUP_KEY, agents)); - }, [agents]); - function generateGroupSets(attributePath: string, groupAgents: Agent[]) { - const path = attributePath.split('.'); - return groupAgents.reduce((acc, agent) => { - let groupKey = agent; - for (const pathFrag of path) { - if (!groupKey) { - // XXX: can't find the key path on the agent object - return acc; + const opts = [{ label: ALL_AGENTS_GROUP_KEY }]; + if (!allAgentsSelected) { + const selectedSet = new Set(selectedGroups); + const platformOptions = Object.keys(agentGroups.platforms).map((name) => { + const platformOption = { label: name }; + if (selectedSet.has(name)) { + platformOption.checked = 'on'; } - groupKey = groupKey[pathFrag]; - } - if (!acc[groupKey]) { - acc[groupKey] = [agent]; + return platformOption; + }); + opts.push(...platformOptions); + } else { + opts[0].checked = 'on'; + } + setGroupOptions(opts); + // TODO: implement policy picking + }, [selectedGroups, allAgentsSelected, agentGroups.platforms]); + + const onGroupChange = useCallback((newOptions) => { + const newGroupOpts = []; + let allSet = false; + for (const opt of newOptions.filter((o) => o.checked === 'on')) { + if (opt.label === ALL_AGENTS_GROUP_KEY) { + allSet = true; } else { - acc[groupKey].push(agent); + newGroupOpts.push(opt.label); } - return acc; - }, Object.create(null)); - } + } + setAllAgentsSelected(allSet); + setSelectedGroups(newGroupOpts); + }, []); - const [platformOptions, setPlatformOptions] = useState([]); - useEffect(() => { - const newOptions = Object.keys(platforms).map((label) => ({ label })); - setPlatformOptions(newOptions); - }, [platforms]); + const { data = {} } = useAllAgents({ + activePage: pageIndex, + limit: pageSize, + direction: sortDirection, + sortField, + }); const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback( (newSelectedItems) => { setSelectedItems(newSelectedItems); - if (newSelectedItems.length) { - const newGroupState = generateGroupSets(GROUP_KEY, newSelectedItems); - for (const el of platformOptions) { - if (newGroupState[el.label]?.length === platforms[el.label]?.length) { - el.checked = 'on'; - } else { - el.checked = undefined; - } - } - } else { - for (const el of platformOptions) { - el.checked = undefined; - } + if (onChange) { + onChange(newSelectedItems.map((item) => item._id)); } - // @ts-expect-error - onChange(newSelectedItems.map((item) => item._id)); }, - [onChange, platforms, platformOptions] + [onChange] ); const columns: Array> = useMemo( @@ -211,39 +205,30 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh // @ts-expect-error update types }, [selectedAgents, data.agents, selectedItems.length]); - const onGroupChange = useCallback( - (newOptions) => { - const currentSelectedSet = new Set(selectedItems); - for (let i = 0; i < platformOptions.length; ++i) { - const newOp = newOptions[i]; - const oldOp = platformOptions[i]; - if (newOp.checked !== oldOp.checked) { - const newAgents = platforms[newOp.label]; - const newAgentSet = new Set(newAgents); - if (newOp.checked === 'on') { - const agentDiff = newAgents.filter((a) => !currentSelectedSet.has(a)); - selectedItems.push(...agentDiff); - tableRef.current.setSelection(selectedItems); - } else { - const newSelection = selectedItems.filter((a) => !newAgentSet.has(a)); - tableRef.current.setSelection(newSelection); - } - break; - } - } - setPlatformOptions(newOptions); - }, - [selectedItems, platformOptions, platforms] - ); - - // useEffect(() => { - // if (selectedAgents && agents && selectedItems.length !== selectedAgents.length) { - // tableRef?.current?.setSelection( - // // @ts-expect-error - // selectedAgents.map((agentId) => find({ _id: agentId }, agents)) - // ); - // } - // }, [selectedAgents, agents, selectedItems.length]); + // const onGroupChange = useCallback( + // (newOptions) => { + // const currentSelectedSet = new Set(selectedItems); + // for (let i = 0; i < platformOptions.length; ++i) { + // const newOp = newOptions[i]; + // const oldOp = platformOptions[i]; + // if (newOp.checked !== oldOp.checked) { + // const newAgents = platforms[newOp.label]; + // const newAgentSet = new Set(newAgents); + // if (newOp.checked === 'on') { + // const agentDiff = newAgents.filter((a) => !currentSelectedSet.has(a)); + // selectedItems.push(...agentDiff); + // tableRef.current.setSelection(selectedItems); + // } else { + // const newSelection = selectedItems.filter((a) => !newAgentSet.has(a)); + // tableRef.current.setSelection(newSelection); + // } + // break; + // } + // } + // setPlatformOptions(newOptions); + // }, + // [selectedItems, platformOptions, platforms] + // ); let modal; @@ -260,7 +245,7 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh aria-label="Searchable example" searchable searchProps={searchProps} - options={platformOptions} + options={groupOptions} onChange={onGroupChange} > {(list, search) => ( @@ -270,20 +255,22 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh )} - - ref={tableRef} - // @ts-expect-error update types - // eslint-disable-next-line react-perf/jsx-no-new-array-as-prop - items={data.agents ?? []} - itemId="_id" - columns={columns} - pagination={pagination} - sorting={sorting} - isSelectable={true} - selection={selection} - onChange={onTableChange} - rowHeader="firstName" - /> + {allAgentsSelected || selectedGroups?.length ? null : ( + + ref={tableRef} + // @ts-expect-error update types + // eslint-disable-next-line react-perf/jsx-no-new-array-as-prop + items={data.agents ?? []} + itemId="_id" + columns={columns} + pagination={pagination} + sorting={sorting} + isSelectable={true} + selection={selection} + onChange={onTableChange} + rowHeader="firstName" + /> + )} @@ -299,8 +286,13 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh } let buttonText; - const numAgents = selectedAgents.length; - if (numAgents > 0) { + if (allAgentsSelected) { + buttonText = 'All Agents Selected'; + } else if (selectedGroups.length) { + const numGroups = selectedGroups.length; + buttonText = `${numGroups} Agent Group${numGroups > 1 ? 's' : ''} Selected`; + } else if (selectedAgents.length > 0) { + const numAgents = selectedAgents.length; buttonText = `${numAgents} Agent${numAgents > 1 ? 's' : ''} Selected`; } else { buttonText = 'Select Agents'; diff --git a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts new file mode 100644 index 0000000000000..0c9d6421c7ea8 --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -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. + */ + +export const ALL_AGENTS_GROUP_KEY = 'All agents'; + +export const useAgentGroups = () => { + // TODO: make this populated by ES queries + return { + [ALL_AGENTS_GROUP_KEY]: 'all_agents', + platforms: { + MacOS: 'darwin', + Windows: 'windows', + Linux: 'linux', + }, + policies: {}, + }; +}; From 45922cbc0621e67986a471e27bd893a902484948 Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 17 Feb 2021 23:23:47 -0800 Subject: [PATCH 3/5] checkpoint --- .../services/epm/registry/registry_url.ts | 18 ++-- .../search_strategy/osquery/agents/index.ts | 11 +++ .../common/search_strategy/osquery/index.ts | 1 + .../osquery/public/agents/agents_table.tsx | 84 +++++++++-------- .../osquery/public/agents/translations.ts | 8 ++ .../osquery/public/agents/use_agent_groups.ts | 90 +++++++++++++++++-- .../osquery/factory/agents/index.ts | 1 + .../factory/agents/query.all_agents.dsl.ts | 13 +++ 8 files changed, 164 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts index 8c637006fb0cd..e81e87a31d985 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts @@ -11,23 +11,21 @@ import { appContextService, licenseService } from '../../'; // the unused variables cause a TS warning about unused values // chose to comment them out vs @ts-ignore or @ts-expect-error on each line -const PRODUCTION_REGISTRY_URL_CDN = 'https://epr.elastic.co'; -const STAGING_REGISTRY_URL_CDN = 'https://epr-staging.elastic.co'; -const SNAPSHOT_REGISTRY_URL_CDN = 'https://epr-snapshot.elastic.co'; +const PRODUCTION_REGISTRY_URL_CDN = 'http://localhost:49153'; +// const STAGING_REGISTRY_URL_CDN = 'https://epr-staging.elastic.co'; +const SNAPSHOT_REGISTRY_URL_CDN = 'http://localhost:49153'; // const PRODUCTION_REGISTRY_URL_NO_CDN = 'https://epr.ea-web.elastic.dev'; // const STAGING_REGISTRY_URL_NO_CDN = 'https://epr-staging.ea-web.elastic.dev'; // const SNAPSHOT_REGISTRY_URL_NO_CDN = 'https://epr-snapshot.ea-web.elastic.dev'; const getDefaultRegistryUrl = (): string => { - const branch = appContextService.getKibanaBranch(); - if (branch === 'master') { + //const branch = appContextService.getKibanaBranch(); + //if (branch === 'master') { return SNAPSHOT_REGISTRY_URL_CDN; - } else if (appContextService.getKibanaVersion().includes('-SNAPSHOT')) { - return STAGING_REGISTRY_URL_CDN; - } else { - return PRODUCTION_REGISTRY_URL_CDN; - } + //} else { + //return PRODUCTION_REGISTRY_URL_CDN; + //} }; export const getRegistryUrl = (): string => { diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts index 57c7a42f2a481..884e49b8bd258 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts @@ -11,9 +11,20 @@ import { Inspect, Maybe, PageInfoPaginated } from '../../common'; import { RequestOptionsPaginated } from '../..'; import { Agent } from '../../../shared_imports'; +export interface AggregationDataPoint { + key: string; +} + +export interface AgentAggregation { + [key: string]: { + buckets: AggregationDataPoint[] + } +} + export interface AgentsStrategyResponse extends IEsSearchResponse { edges: Agent[]; totalCount: number; + aggregations?: AgentAggregation; pageInfo: PageInfoPaginated; inspect?: Maybe; } diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts index 567990aca0537..8314b488fc9cb 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts @@ -36,6 +36,7 @@ export type FactoryQueryTypes = OsqueryQueries; export interface RequestBasicOptions extends IEsSearchRequest { filterQuery: ESQuery | string | undefined; + aggregations?: {[key: string]: string}; docValueFields?: DocValueFields[]; factoryQueryType?: FactoryQueryTypes; } diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 2b77fe332f300..e9a8ea052cc7d 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -19,6 +19,7 @@ import { EuiModalFooter, EuiOverlayMask, EuiSelectable, + EuiSelectableOption, EuiButton, EuiButtonEmpty, EuiHealth, @@ -34,6 +35,8 @@ interface AgentsTableProps { onChange: (payload: string[]) => void; } +type GroupOption = EuiSelectableOption<{}>; + const AgentsTableComponent: React.FC = ({ selectedAgents, onChange }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(5); @@ -66,44 +69,38 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh return {label}; }; - const agentGroups = useAgentGroups(); - const [selectedGroups, setSelectedGroups] = useState([]); - const [allAgentsSelected, setAllAgentsSelected] = useState(false); - - const [groupOptions, setGroupOptions] = useState([]); - + const { loading: groupsLoading, groups } = useAgentGroups(); + const [selectedGroups, setSelectedGroups] = useState([]); + const [allAgentsSelected, setAllAgentsSelected] = useState(false); + const [groupOptions, setGroupOptions] = useState([]); useEffect(() => { - const opts = [{ label: ALL_AGENTS_GROUP_KEY }]; + const opts: GroupOption[] = [{ label: ALL_AGENTS_GROUP_KEY }]; if (!allAgentsSelected) { - const selectedSet = new Set(selectedGroups); - const platformOptions = Object.keys(agentGroups.platforms).map((name) => { - const platformOption = { label: name }; + const selectedSet = new Set(selectedGroups); + const generateOption = (name: string) => { + const platformOption: GroupOption = { label: name }; if (selectedSet.has(name)) { platformOption.checked = 'on'; } return platformOption; - }); + } + const platformOptions = groups.platforms.map(generateOption); opts.push(...platformOptions); + const policyOptions = groups.policies.map(generateOption); + opts.push(...policyOptions); } else { opts[0].checked = 'on'; } setGroupOptions(opts); // TODO: implement policy picking - }, [selectedGroups, allAgentsSelected, agentGroups.platforms]); + }, [groups.policies, groups.platforms, selectedGroups]); - const onGroupChange = useCallback((newOptions) => { - const newGroupOpts = []; - let allSet = false; - for (const opt of newOptions.filter((o) => o.checked === 'on')) { - if (opt.label === ALL_AGENTS_GROUP_KEY) { - allSet = true; - } else { - newGroupOpts.push(opt.label); - } - } - setAllAgentsSelected(allSet); - setSelectedGroups(newGroupOpts); - }, []); + const onGroupChange = useCallback((newOptions: GroupOption[]) => { + const selected = newOptions.filter(el => el.checked === 'on').map(el => el.label) + setSelectedGroups(selected) + setAllAgentsSelected(selected.some(el => el === ALL_AGENTS_GROUP_KEY)) + onChange([]) + }, [onChange]); const { data = {} } = useAllAgents({ activePage: pageIndex, @@ -115,9 +112,7 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback( (newSelectedItems) => { setSelectedItems(newSelectedItems); - if (onChange) { - onChange(newSelectedItems.map((item) => item._id)); - } + onChange(newSelectedItems.map((item: {_id: string}) => item._id)); }, [onChange] ); @@ -237,24 +232,27 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh - Modal title + Select Agents - - {(list, search) => ( - - {search} - {list} - - )} - + {groupsLoading + ? null + : + {(list, search) => ( + + {search} + {list} + + )} + + } {allAgentsSelected || selectedGroups?.length ? null : ( ref={tableRef} diff --git a/x-pack/plugins/osquery/public/agents/translations.ts b/x-pack/plugins/osquery/public/agents/translations.ts index 0d9d9a8a12b8f..2499bd1652bff 100644 --- a/x-pack/plugins/osquery/public/agents/translations.ts +++ b/x-pack/plugins/osquery/public/agents/translations.ts @@ -14,3 +14,11 @@ export const ERROR_ALL_AGENTS = i18n.translate('xpack.osquery.agents.errorSearch export const FAIL_ALL_AGENTS = i18n.translate('xpack.osquery.agents.failSearchDescription', { defaultMessage: `Failed to fetch agents`, }); + +export const ERROR_AGENT_GROUPS = i18n.translate('xpack.osquery.agents.errorSearchDescription', { + defaultMessage: `An error has occurred while fetching agent groups.`, +}); + +export const FAIL_AGENT_GROUPS = i18n.translate('xpack.osquery.agents.errorSearchDescription', { + defaultMessage: `An error has occurred while fetching agent groups.`, +}); diff --git a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts index 0c9d6421c7ea8..d92e931b41dfe 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -4,18 +4,90 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { useEffect, useState, useRef, useMemo } from 'react'; +import { useKibana } from '../common/lib/kibana'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; -export const ALL_AGENTS_GROUP_KEY = 'All agents'; +import { + OsqueryQueries, + AgentsRequestOptions, + AgentsStrategyResponse, +} from '../../common/search_strategy'; + +import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common'; +import { generateTablePaginationOptions } from './helpers'; +import * as i18n from './translations'; +export const ALL_AGENTS_GROUP_KEY = 'All agents'; export const useAgentGroups = () => { - // TODO: make this populated by ES queries + const { data, notifications } = useKibana().services; + const [loading, setLoading] = useState(true); + const [platforms, setPlatforms] = useState([]) + const [policies, setPolicies] = useState([]) + + const abortCtrl = useRef(new AbortController()); + useEffect(() => { + let didCancel = false; + const searchSubscription$ = data.search + .search({ + filterQuery: undefined, + factoryQueryType: OsqueryQueries.agents, + aggregations: { + platforms: "local_metadata.os.platform", + policies: "policy_id" + }, + pagination: generateTablePaginationOptions(0, 9000), + sort: { + direction: 'asc', + field: 'local_metadata.os.platform' + } + } as AgentsRequestOptions, { + strategy: 'osquerySearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + if (!didCancel) { + setLoading(false); + console.log(response) + if (response.aggregations) { + const aggs = response.aggregations + setPlatforms( + aggs.platforms.buckets.map(o => o.key) + ) + setPolicies( + aggs.policies.buckets.map(o => o.key) + ) + } + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_AGENT_GROUPS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_AGENT_GROUPS, text: msg.message }); + } + }, + }); + return () => { + didCancel = true; + abortCtrl.current.abort() + } + }, [setPolicies, setPlatforms]) return { - [ALL_AGENTS_GROUP_KEY]: 'all_agents', - platforms: { - MacOS: 'darwin', - Windows: 'windows', - Linux: 'linux', - }, - policies: {}, + loading, + groups: { + [ALL_AGENTS_GROUP_KEY]: [], + platforms, + policies, + } }; }; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts index 1f7fbccb68682..395b3d6504fa8 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts @@ -38,6 +38,7 @@ export const allAgents: OsqueryFactory = { ...response, inspect, edges: response.rawResponse.hits.hits.map((hit) => ({ _id: hit._id, ...hit._source })), + aggregations: response.rawResponse.aggregations, totalCount: response.rawResponse.hits.total, pageInfo: { activePage: activePage ?? 0, diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts index 4ad6022017966..34caf0463c88f 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts @@ -14,6 +14,7 @@ export const buildAgentsQuery = ({ filterQuery, pagination: { cursorStart, querySize }, sort, + aggregations, }: AgentsRequestOptions): ISearchRequestParams => { // const filter = [...createQueryFilterClauses(filterQuery)]; @@ -29,6 +30,7 @@ export const buildAgentsQuery = ({ }, }, }, + aggs: {}, track_total_hits: true, sort: [ { @@ -42,5 +44,16 @@ export const buildAgentsQuery = ({ }, }; + if (aggregations) { + dslQuery.body.aggs = Object.keys(aggregations).reduce((acc, aggKey) => { + acc[aggKey] = { + terms: { + field: aggregations[aggKey] + } + } + return acc + }, {} as {[key: string]: {terms: {field: string}}}) + } + return dslQuery; }; From 21c3e572051c4a6c5bee648f484f99ce39e1dbc0 Mon Sep 17 00:00:00 2001 From: bryan Date: Thu, 18 Feb 2021 01:59:28 -0800 Subject: [PATCH 4/5] resolve grouping to agent id --- .../services/epm/registry/registry_url.ts | 18 +-- .../search_strategy/osquery/agents/index.ts | 4 +- .../common/search_strategy/osquery/index.ts | 2 +- .../osquery/public/agents/agents_table.tsx | 118 ++++++++++-------- .../osquery/public/agents/use_agent_groups.ts | 59 +++++---- .../live_query/form/agents_table_field.tsx | 6 +- .../osquery/public/live_query/form/index.tsx | 2 +- .../routes/action/create_action_route.ts | 61 ++++++++- .../factory/agents/query.all_agents.dsl.ts | 12 +- 9 files changed, 175 insertions(+), 107 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts index e81e87a31d985..8c637006fb0cd 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts @@ -11,21 +11,23 @@ import { appContextService, licenseService } from '../../'; // the unused variables cause a TS warning about unused values // chose to comment them out vs @ts-ignore or @ts-expect-error on each line -const PRODUCTION_REGISTRY_URL_CDN = 'http://localhost:49153'; -// const STAGING_REGISTRY_URL_CDN = 'https://epr-staging.elastic.co'; -const SNAPSHOT_REGISTRY_URL_CDN = 'http://localhost:49153'; +const PRODUCTION_REGISTRY_URL_CDN = 'https://epr.elastic.co'; +const STAGING_REGISTRY_URL_CDN = 'https://epr-staging.elastic.co'; +const SNAPSHOT_REGISTRY_URL_CDN = 'https://epr-snapshot.elastic.co'; // const PRODUCTION_REGISTRY_URL_NO_CDN = 'https://epr.ea-web.elastic.dev'; // const STAGING_REGISTRY_URL_NO_CDN = 'https://epr-staging.ea-web.elastic.dev'; // const SNAPSHOT_REGISTRY_URL_NO_CDN = 'https://epr-snapshot.ea-web.elastic.dev'; const getDefaultRegistryUrl = (): string => { - //const branch = appContextService.getKibanaBranch(); - //if (branch === 'master') { + const branch = appContextService.getKibanaBranch(); + if (branch === 'master') { return SNAPSHOT_REGISTRY_URL_CDN; - //} else { - //return PRODUCTION_REGISTRY_URL_CDN; - //} + } else if (appContextService.getKibanaVersion().includes('-SNAPSHOT')) { + return STAGING_REGISTRY_URL_CDN; + } else { + return PRODUCTION_REGISTRY_URL_CDN; + } }; export const getRegistryUrl = (): string => { diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts index 884e49b8bd258..83625897bc96c 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts @@ -17,8 +17,8 @@ export interface AggregationDataPoint { export interface AgentAggregation { [key: string]: { - buckets: AggregationDataPoint[] - } + buckets: AggregationDataPoint[]; + }; } export interface AgentsStrategyResponse extends IEsSearchResponse { diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts index 8314b488fc9cb..4bf06472125f5 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts @@ -36,7 +36,7 @@ export type FactoryQueryTypes = OsqueryQueries; export interface RequestBasicOptions extends IEsSearchRequest { filterQuery: ESQuery | string | undefined; - aggregations?: {[key: string]: string}; + aggregations?: { [key: string]: string }; docValueFields?: DocValueFields[]; factoryQueryType?: FactoryQueryTypes; } diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index e9a8ea052cc7d..b855d73b1bde8 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -30,14 +30,21 @@ import { useAgentGroups, ALL_AGENTS_GROUP_KEY } from './use_agent_groups'; import { Direction } from '../../common/search_strategy'; import { Agent } from '../../common/shared_imports'; +export interface AgentsSelection { + agents: string[]; + allAgentsSelected: boolean; + platformsSelected: string[]; + policiesSelected: string[]; +} + interface AgentsTableProps { - selectedAgents: string[]; - onChange: (payload: string[]) => void; + agentSelection: AgentsSelection; + onChange: (payload: AgentsSelection) => void; } -type GroupOption = EuiSelectableOption<{}>; +type GroupOption = EuiSelectableOption<{ type: string }>; -const AgentsTableComponent: React.FC = ({ selectedAgents, onChange }) => { +const AgentsTableComponent: React.FC = ({ agentSelection, onChange }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(5); const [sortField, setSortField] = useState('upgraded_at'); @@ -74,33 +81,58 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh const [allAgentsSelected, setAllAgentsSelected] = useState(false); const [groupOptions, setGroupOptions] = useState([]); useEffect(() => { - const opts: GroupOption[] = [{ label: ALL_AGENTS_GROUP_KEY }]; + const opts: GroupOption[] = [{ label: ALL_AGENTS_GROUP_KEY, type: 'all' }]; if (!allAgentsSelected) { - const selectedSet = new Set(selectedGroups); - const generateOption = (name: string) => { - const platformOption: GroupOption = { label: name }; + const selectedSet = new Set(selectedGroups); + const generateOption = (type: string) => (name: string) => { + const option: GroupOption = { label: name, type }; if (selectedSet.has(name)) { - platformOption.checked = 'on'; + option.checked = 'on'; } - return platformOption; - } - const platformOptions = groups.platforms.map(generateOption); + return option; + }; + const platformOptions = groups.platforms.map(generateOption('platform')); opts.push(...platformOptions); - const policyOptions = groups.policies.map(generateOption); + const policyOptions = groups.policies.map(generateOption('policy')); opts.push(...policyOptions); } else { opts[0].checked = 'on'; } setGroupOptions(opts); // TODO: implement policy picking - }, [groups.policies, groups.platforms, selectedGroups]); + }, [groups.policies, groups.platforms, selectedGroups, allAgentsSelected]); - const onGroupChange = useCallback((newOptions: GroupOption[]) => { - const selected = newOptions.filter(el => el.checked === 'on').map(el => el.label) - setSelectedGroups(selected) - setAllAgentsSelected(selected.some(el => el === ALL_AGENTS_GROUP_KEY)) - onChange([]) - }, [onChange]); + const onGroupChange = useCallback( + (newOptions: GroupOption[]) => { + const selectedPlatforms: string[] = []; + const selectedPolicies: string[] = []; + newOptions.forEach((opt) => { + if (opt.checked === 'on') { + switch (opt.type) { + case 'platform': + selectedPlatforms.push(opt.label); + break; + case 'policy': + selectedPolicies.push(opt.label); + break; + default: + break; + } + } + }); + const selected = newOptions.filter((el) => el.checked === 'on').map((el) => el.label); + setSelectedGroups(selected); + const allSelected = selected.some((el) => el === ALL_AGENTS_GROUP_KEY); + setAllAgentsSelected(allSelected); + onChange({ + ...agentSelection, + allAgentsSelected: allSelected, + platformsSelected: selectedPlatforms, + policiesSelected: selectedPolicies, + }); + }, + [onChange, agentSelection] + ); const { data = {} } = useAllAgents({ activePage: pageIndex, @@ -112,9 +144,12 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback( (newSelectedItems) => { setSelectedItems(newSelectedItems); - onChange(newSelectedItems.map((item: {_id: string}) => item._id)); + onChange({ + ...agentSelection, + agents: newSelectedItems.map((item: { _id: string }) => item._id), + }); }, - [onChange] + [onChange, agentSelection] ); const columns: Array> = useMemo( @@ -186,6 +221,7 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh ); useEffect(() => { + const selectedAgents = agentSelection?.agents; if ( selectedAgents?.length && // @ts-expect-error update types @@ -198,32 +234,7 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh ); } // @ts-expect-error update types - }, [selectedAgents, data.agents, selectedItems.length]); - - // const onGroupChange = useCallback( - // (newOptions) => { - // const currentSelectedSet = new Set(selectedItems); - // for (let i = 0; i < platformOptions.length; ++i) { - // const newOp = newOptions[i]; - // const oldOp = platformOptions[i]; - // if (newOp.checked !== oldOp.checked) { - // const newAgents = platforms[newOp.label]; - // const newAgentSet = new Set(newAgents); - // if (newOp.checked === 'on') { - // const agentDiff = newAgents.filter((a) => !currentSelectedSet.has(a)); - // selectedItems.push(...agentDiff); - // tableRef.current.setSelection(selectedItems); - // } else { - // const newSelection = selectedItems.filter((a) => !newAgentSet.has(a)); - // tableRef.current.setSelection(newSelection); - // } - // break; - // } - // } - // setPlatformOptions(newOptions); - // }, - // [selectedItems, platformOptions, platforms] - // ); + }, [agentSelection, data.agents, selectedItems.length]); let modal; @@ -236,9 +247,8 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh - {groupsLoading - ? null - : = ({ selectedAgents, onCh )} - } + )} {allAgentsSelected || selectedGroups?.length ? null : ( ref={tableRef} @@ -289,8 +299,8 @@ const AgentsTableComponent: React.FC = ({ selectedAgents, onCh } else if (selectedGroups.length) { const numGroups = selectedGroups.length; buttonText = `${numGroups} Agent Group${numGroups > 1 ? 's' : ''} Selected`; - } else if (selectedAgents.length > 0) { - const numAgents = selectedAgents.length; + } else if (agentSelection?.agents?.length) { + const numAgents = agentSelection.agents.length; buttonText = `${numAgents} Agent${numAgents > 1 ? 's' : ''} Selected`; } else { buttonText = 'Select Agents'; diff --git a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts index d92e931b41dfe..adfbe6cc28379 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useEffect, useState, useRef, useMemo } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { useKibana } from '../common/lib/kibana'; import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; @@ -22,43 +22,41 @@ export const ALL_AGENTS_GROUP_KEY = 'All agents'; export const useAgentGroups = () => { const { data, notifications } = useKibana().services; const [loading, setLoading] = useState(true); - const [platforms, setPlatforms] = useState([]) - const [policies, setPolicies] = useState([]) + const [platforms, setPlatforms] = useState([]); + const [policies, setPolicies] = useState([]); const abortCtrl = useRef(new AbortController()); useEffect(() => { let didCancel = false; const searchSubscription$ = data.search - .search({ - filterQuery: undefined, - factoryQueryType: OsqueryQueries.agents, - aggregations: { - platforms: "local_metadata.os.platform", - policies: "policy_id" - }, - pagination: generateTablePaginationOptions(0, 9000), - sort: { - direction: 'asc', - field: 'local_metadata.os.platform' + .search( + { + filterQuery: undefined, + factoryQueryType: OsqueryQueries.agents, + aggregations: { + platforms: 'local_metadata.os.platform', + policies: 'policy_id', + }, + pagination: generateTablePaginationOptions(0, 9000), + sort: { + direction: 'asc', + field: 'local_metadata.os.platform', + }, + } as AgentsRequestOptions, + { + strategy: 'osquerySearchStrategy', + abortSignal: abortCtrl.current.signal, } - } as AgentsRequestOptions, { - strategy: 'osquerySearchStrategy', - abortSignal: abortCtrl.current.signal, - }) + ) .subscribe({ next: (response) => { if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); - console.log(response) if (response.aggregations) { - const aggs = response.aggregations - setPlatforms( - aggs.platforms.buckets.map(o => o.key) - ) - setPolicies( - aggs.policies.buckets.map(o => o.key) - ) + const aggs = response.aggregations; + setPlatforms(aggs.platforms.buckets.map((o) => o.key)); + setPolicies(aggs.policies.buckets.map((o) => o.key)); } } searchSubscription$.unsubscribe(); @@ -77,17 +75,18 @@ export const useAgentGroups = () => { } }, }); + const abort = abortCtrl.current; return () => { didCancel = true; - abortCtrl.current.abort() - } - }, [setPolicies, setPlatforms]) + abort.abort(); + }; + }, [setPolicies, setPlatforms, data.search, notifications.toasts]); return { loading, groups: { [ALL_AGENTS_GROUP_KEY]: [], platforms, policies, - } + }, }; }; diff --git a/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx b/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx index 7a93b5d2491db..4bc9262af7613 100644 --- a/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx +++ b/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx @@ -7,10 +7,10 @@ import React, { useCallback } from 'react'; import { FieldHook } from '../../shared_imports'; -import { AgentsTable } from '../../agents/agents_table'; +import { AgentsTable, AgentsSelection } from '../../agents/agents_table'; interface AgentsTableFieldProps { - field: FieldHook; + field: FieldHook; } const AgentsTableFieldComponent: React.FC = ({ field }) => { @@ -24,7 +24,7 @@ const AgentsTableFieldComponent: React.FC = ({ field }) = [value, setValue] ); - return ; + return ; }; export const AgentsTableField = React.memo(AgentsTableFieldComponent); diff --git a/x-pack/plugins/osquery/public/live_query/form/index.tsx b/x-pack/plugins/osquery/public/live_query/form/index.tsx index 38677f4a323d1..b584d60bf63d0 100644 --- a/x-pack/plugins/osquery/public/live_query/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_query/form/index.tsx @@ -32,7 +32,7 @@ const LiveQueryFormComponent: React.FC = ({ onSubmit }) => { return (
- + {'Send query'} diff --git a/x-pack/plugins/osquery/server/routes/action/create_action_route.ts b/x-pack/plugins/osquery/server/routes/action/create_action_route.ts index 4ec5bc2a192cc..5f0deb67c6a39 100644 --- a/x-pack/plugins/osquery/server/routes/action/create_action_route.ts +++ b/x-pack/plugins/osquery/server/routes/action/create_action_route.ts @@ -11,6 +11,13 @@ import moment from 'moment'; import { IRouter } from '../../../../../../src/core/server'; +export interface AgentsSelection { + agents: string[]; + allAgentsSelected: boolean; + platformsSelected: string[]; + policiesSelected: string[]; +} + export const createActionRoute = (router: IRouter) => { router.post( { @@ -22,14 +29,64 @@ export const createActionRoute = (router: IRouter) => { }, async (context, request, response) => { const esClient = context.core.elasticsearch.client.asInternalUser; + const selectedAgents: string[] = []; + const { + agentSelection: { allAgentsSelected, platformsSelected, policiesSelected, agents }, + } = request.body as { agentSelection: AgentsSelection }; + const extractIds = ({ body }) => + body.hits.hits.map((o) => o._source.local_metadata.elastic.agent.id); + if (allAgentsSelected) { + // make a query for all agent ids + const ids = extractIds( + await esClient.search<{}, {}>({ + index: '.fleet-agents', + body: { + _source: 'local_metadata.elastic.agent.id', + size: 9000, + query: { + match_all: {}, + }, + }, + }) + ); + selectedAgents.push(...ids); + } else if (platformsSelected.length > 0 || policiesSelected.length > 0) { + const filters: Array<{ + term: { [key: string]: string }; + }> = platformsSelected.map((platform) => ({ + term: { 'local_metadata.os.platform': platform }, + })); + filters.push(...policiesSelected.map((policyId) => ({ term: { policyId } }))); + const query = { + index: '.fleet-agents', + body: { + _source: 'local_metadata.elastic.agent.id', + size: 9000, + query: { + bool: { + filter: [ + { + bool: { + should: filters, + }, + }, + ], + }, + }, + }, + }; + const ids = extractIds(await esClient.search<{}, {}>(query)); + selectedAgents.push(...ids); + } else { + selectedAgents.push(...agents); + } const action = { action_id: uuid.v4(), '@timestamp': moment().toISOString(), expiration: moment().add(2, 'days').toISOString(), type: 'INPUT_ACTION', input_type: 'osquery', - // @ts-expect-error update validation - agents: request.body.agents, + agents: selectedAgents, data: { id: uuid.v4(), // @ts-expect-error update validation diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts index 34caf0463c88f..1b1e296abece9 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts @@ -45,14 +45,14 @@ export const buildAgentsQuery = ({ }; if (aggregations) { - dslQuery.body.aggs = Object.keys(aggregations).reduce((acc, aggKey) => { + Object.keys(aggregations).reduce((acc, aggKey) => { acc[aggKey] = { terms: { - field: aggregations[aggKey] - } - } - return acc - }, {} as {[key: string]: {terms: {field: string}}}) + field: aggregations[aggKey], + }, + }; + return acc; + }, dslQuery.body.aggs as { [key: string]: { terms: { field: string } } }); } return dslQuery; From 79a01ff19b83ab267d9d6c0e5fc1c34cc00852d8 Mon Sep 17 00:00:00 2001 From: bryan Date: Thu, 18 Feb 2021 23:56:39 -0800 Subject: [PATCH 5/5] remove vestigial comment --- x-pack/plugins/osquery/public/agents/agents_table.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index b855d73b1bde8..c3cafbf717c8e 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -99,7 +99,6 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh opts[0].checked = 'on'; } setGroupOptions(opts); - // TODO: implement policy picking }, [groups.policies, groups.platforms, selectedGroups, allAgentsSelected]); const onGroupChange = useCallback(