From e1e6b56fe90ab045fa28a8adc8544d2d4037d9e7 Mon Sep 17 00:00:00 2001 From: bryan Date: Tue, 13 Apr 2021 23:29:56 -0700 Subject: [PATCH 1/6] clean up + reformating option rendering --- .../osquery/public/agents/agents_table.tsx | 55 ++++++++++--------- .../plugins/osquery/public/agents/helpers.ts | 4 +- x-pack/plugins/osquery/public/agents/types.ts | 3 +- .../osquery/public/agents/use_agent_groups.ts | 14 ++++- .../public/agents/use_agent_policies.ts | 34 ++++++++++++ .../public/agents/use_osquery_policies.ts | 4 +- 6 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 x-pack/plugins/osquery/public/agents/use_agent_policies.ts diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 5f1b6a0d2f0b1..c7d8caee1a1bb 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -28,7 +28,7 @@ import { generateSelectedAgentsMessage, } from './translations'; -import { AGENT_GROUP_KEY, SelectedGroups, AgentOptionValue, GroupOptionValue } from './types'; +import { AGENT_GROUP_KEY, SelectedGroups, AgentOptionValue, GroupOptionValue, Group } from './types'; export interface AgentsSelection { agents: string[]; @@ -46,6 +46,17 @@ type GroupOption = EuiComboBoxOptionOption; const getColor = generateColorPicker(); +const generateOptions = (groupType: AGENT_GROUP_KEY, label: string, collection: Group[]) => { + return { + label, + options: collection.map(({ name, id, size }) => ({ + label: name, + color: getColor(groupType), + value: { groupType, id, size }, + })), + }; +} + const AgentsTableComponent: React.FC = ({ onChange }) => { const osqueryPolicyData = useOsqueryPolicies(); const { loading: groupsLoading, totalCount: totalNumAgents, groups } = useAgentGroups( @@ -74,26 +85,12 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { if (groups.platforms.length > 0) { const groupType = AGENT_GROUP_KEY.Platform; - opts.push({ - label: AGENT_PLATFORMS_LABEL, - options: groups.platforms.map(({ name, size }) => ({ - label: name, - color: getColor(groupType), - value: { groupType, size }, - })), - }); + opts.push(generateOptions(groupType, AGENT_PLATFORMS_LABEL, groups.platforms)) } if (groups.policies.length > 0) { const groupType = AGENT_GROUP_KEY.Policy; - opts.push({ - label: AGENT_POLICY_LABEL, - options: groups.policies.map(({ name, size }) => ({ - label: name, - color: getColor(groupType), - value: { groupType, size }, - })), - }); + opts.push(generateOptions(groupType, AGENT_POLICY_LABEL, groups.policies)) } if (agents && agents.length > 0) { @@ -102,6 +99,7 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { label: AGENT_SELECTION_LABEL, options: (agents as Agent[]).map((agent: Agent) => ({ label: agent.local_metadata.host.hostname, + key: agent.local_metadata.elastic.agent.id, color: getColor(groupType), value: { groupType, @@ -126,7 +124,7 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { policiesSelected: [], }; // parse through the selections to be able to determine how many are actually selected - const selectedAgents = []; + const selectedAgents: AgentOptionValue[] = []; const selectedGroups: SelectedGroups = { policy: {}, platform: {}, @@ -144,7 +142,7 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { value = opt.value as GroupOptionValue; if (!newAgentSelection.allAgentsSelected) { // we don't need to calculate diffs when all agents are selected - selectedGroups.platform[opt.label] = value.size; + selectedGroups.platform[opt.value?.id ?? opt.label] = value.size; } newAgentSelection.platformsSelected.push(opt.label); break; @@ -152,7 +150,7 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { value = opt.value as GroupOptionValue; if (!newAgentSelection.allAgentsSelected) { // we don't need to calculate diffs when all agents are selected - selectedGroups.policy[opt.label] = value.size ?? 0; + selectedGroups.policy[opt.value?.id ?? opt.label] = value.size; } newAgentSelection.policiesSelected.push(opt.label); break; @@ -160,10 +158,11 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { value = opt.value as AgentOptionValue; if (!newAgentSelection.allAgentsSelected) { // we don't need to count how many agents are selected if they are all selected - selectedAgents.push(opt.value); + selectedAgents.push(value); + } + if (value?.id) { + newAgentSelection.agents.push(value.id); } - // TODO: fix this casting by updating the opt type to be a union - newAgentSelection.agents.push(value.id as string); break; default: // this should never happen! @@ -177,7 +176,7 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { const checkAgent = generateAgentCheck(selectedGroups); setNumAgentsSelected( // filter out all the agents counted by selected policies and platforms - selectedAgents.filter((a) => checkAgent(a as AgentOptionValue)).length + + selectedAgents.filter(checkAgent).length + // add the number of agents added via policy and platform groups getNumAgentsInGrouping(selectedGroups) - // subtract the number of agents double counted by policy/platform selections @@ -191,18 +190,22 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { ); const renderOption = useCallback((option, searchValue, contentClassName) => { - const { label, value } = option; + const { label, value, key } = option; return value?.groupType === AGENT_GROUP_KEY.Agent ? ( {label} +   + ({key}) ) : ( + [{value?.size}] +   {label}   - ({value?.size}) + {value?.id && label !== value?.id && (({value?.id}))} ); }, []); diff --git a/x-pack/plugins/osquery/public/agents/helpers.ts b/x-pack/plugins/osquery/public/agents/helpers.ts index 830fca5f57caa..22817799da986 100644 --- a/x-pack/plugins/osquery/public/agents/helpers.ts +++ b/x-pack/plugins/osquery/public/agents/helpers.ts @@ -43,11 +43,11 @@ export const processAggregations = (aggs: Record) => { const platformTerms = aggs.platforms as TermsAggregate; const policyTerms = aggs.policies as TermsAggregate; - const policies = policyTerms?.buckets.map((o) => ({ name: o.key, size: o.doc_count })) ?? []; + const policies = policyTerms?.buckets.map((o) => ({ name: o.key, id: o.key, size: o.doc_count })) ?? []; if (platformTerms?.buckets) { for (const { key, doc_count: size, policies: platformPolicies } of platformTerms.buckets) { - platforms.push({ name: key, size }); + platforms.push({ name: key, id: key, size }); if (platformPolicies?.buckets && policies.length > 0) { overlap[key] = platformPolicies.buckets.reduce((acc: { [key: string]: number }, pol) => { acc[pol.key] = pol.doc_count; diff --git a/x-pack/plugins/osquery/public/agents/types.ts b/x-pack/plugins/osquery/public/agents/types.ts index 2fa8ddaf345cd..190bd26cffcdb 100644 --- a/x-pack/plugins/osquery/public/agents/types.ts +++ b/x-pack/plugins/osquery/public/agents/types.ts @@ -17,6 +17,7 @@ export type AggregationDataPoint = BaseDataPoint & { }; export interface Group { + id: string; name: string; size: number; } @@ -29,13 +30,13 @@ export interface SelectedGroups { } interface BaseGroupOption { + id?: string; groupType: AGENT_GROUP_KEY; } export type AgentOptionValue = BaseGroupOption & { groups: { [groupType: string]: string }; online: boolean; - id: string; }; export type GroupOptionValue = BaseGroupOption & { 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 0eaca65d02d4b..84115a5c78e7d 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -7,6 +7,7 @@ import { useState } from 'react'; import { useQuery } from 'react-query'; import { useKibana } from '../common/lib/kibana'; +import { useAgentPolicies } from './use_agent_policies'; import { OsqueryQueries, @@ -25,6 +26,7 @@ interface UseAgentGroups { export const useAgentGroups = ({ osqueryPolicies, osqueryPoliciesLoading }: UseAgentGroups) => { const { data } = useKibana().services; + const { agentPoliciesLoading, agentPolicyById } = useAgentPolicies(osqueryPolicies) const [platforms, setPlatforms] = useState([]); const [policies, setPolicies] = useState([]); const [loading, setLoading] = useState(true); @@ -76,16 +78,22 @@ export const useAgentGroups = ({ osqueryPolicies, osqueryPoliciesLoading }: UseA policies: newPolicies, } = processAggregations(responseData.rawResponse.aggregations); - setPlatforms(newPlatforms); + setPlatforms(newPlatforms) setOverlap(newOverlap); - setPolicies(newPolicies); + setPolicies(newPolicies.map(p => { + const name = agentPolicyById[p.id]?.name ?? p.name + return { + ...p, + name + } + })); } setLoading(false); setTotalCount(responseData.totalCount); }, { - enabled: !osqueryPoliciesLoading, + enabled: !osqueryPoliciesLoading && !agentPoliciesLoading, } ); diff --git a/x-pack/plugins/osquery/public/agents/use_agent_policies.ts b/x-pack/plugins/osquery/public/agents/use_agent_policies.ts new file mode 100644 index 0000000000000..056be8baafba9 --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/use_agent_policies.ts @@ -0,0 +1,34 @@ +/* + * 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 { useQueries, UseQueryResult } from 'react-query'; +import { useKibana } from '../common/lib/kibana'; +import { AgentPolicy, agentPolicyRouteService, GetOneAgentPolicyResponse } from '../../../fleet/common'; + +export const useAgentPolicies = (policyIds: string[] = []) => { + const { http } = useKibana().services; + + const agentResponse = useQueries( + policyIds.map((policyId) => ({ + queryKey: ['agentPolicy', policyId], + queryFn: () => http.get(agentPolicyRouteService.getInfoPath(policyId)), + options: {enabled: policyIds.length > 0} + })), + ) as Array>; + + const agentPoliciesLoading = agentResponse.some(p => p.isLoading) + const agentPolicies = agentResponse.map(p => p.data?.item) + const agentPolicyById = agentPolicies.reduce((acc, p) => { + if (!p) { + return acc + } + acc[p.id] = p + return acc + }, {} as {[key: string]: AgentPolicy}) + + return { agentPoliciesLoading, agentPolicies, agentPolicyById }; +}; diff --git a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts index f786e9167d2f8..d9f51033a14f4 100644 --- a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts +++ b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts @@ -15,11 +15,13 @@ export const useOsqueryPolicies = () => { const { isLoading: osqueryPoliciesLoading, data: osqueryPolicies } = useQuery( ['osqueryPolicies'], async () => { - return await http.get('/api/fleet/package_policies', { + const d = await http.get('/api/fleet/package_policies', { query: { kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:osquery_manager`, }, }); + console.log('init', d) + return d }, { select: (data) => data.items.map((p: { policy_id: string }) => p.policy_id) } ); From 5f1487607b1ad89b34cd8186b6f8f82875440471 Mon Sep 17 00:00:00 2001 From: bryan Date: Fri, 16 Apr 2021 00:44:19 -0700 Subject: [PATCH 2/6] janky infinite loading agents, async searching --- .../osquery/public/agents/agent_grouper.ts | 117 +++++++++++ .../osquery/public/agents/agents_table.tsx | 182 ++++++++---------- x-pack/plugins/osquery/public/agents/types.ts | 3 + .../public/agents/use_agent_policies.ts | 22 ++- .../osquery/public/agents/use_all_agents.ts | 25 ++- .../public/agents/use_osquery_policies.ts | 4 +- 6 files changed, 239 insertions(+), 114 deletions(-) create mode 100644 x-pack/plugins/osquery/public/agents/agent_grouper.ts diff --git a/x-pack/plugins/osquery/public/agents/agent_grouper.ts b/x-pack/plugins/osquery/public/agents/agent_grouper.ts new file mode 100644 index 0000000000000..da32a5621a002 --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/agent_grouper.ts @@ -0,0 +1,117 @@ +/* + * 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 { Agent } from '../../common/shared_imports'; +import { generateColorPicker } from './helpers'; +import { + ALL_AGENTS_LABEL, + AGENT_PLATFORMS_LABEL, + AGENT_POLICY_LABEL, + AGENT_SELECTION_LABEL, +} from './translations'; +import { AGENT_GROUP_KEY, Group, GroupOption } from './types'; + +const getColor = generateColorPicker(); + +const generateGroup = (label: string, groupType: AGENT_GROUP_KEY) => { + return { + label, + groupType, + color: getColor(groupType), + size: 0, + data: [] as T[], + }; +}; + +export class AgentGrouper { + groupOrder = [ + AGENT_GROUP_KEY.All, + AGENT_GROUP_KEY.Platform, + AGENT_GROUP_KEY.Policy, + AGENT_GROUP_KEY.Agent, + ]; + groups = { + [AGENT_GROUP_KEY.All]: generateGroup(ALL_AGENTS_LABEL, AGENT_GROUP_KEY.All), + [AGENT_GROUP_KEY.Platform]: generateGroup(AGENT_PLATFORMS_LABEL, AGENT_GROUP_KEY.Platform), + [AGENT_GROUP_KEY.Policy]: generateGroup(AGENT_POLICY_LABEL, AGENT_GROUP_KEY.Policy), + [AGENT_GROUP_KEY.Agent]: generateGroup(AGENT_SELECTION_LABEL, AGENT_GROUP_KEY.Agent), + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + updateGroup(key: AGENT_GROUP_KEY, data: any[], append = false) { + if (!data?.length) { + return; + } + const group = this.groups[key]; + if (append) { + group.data.push(...data); + } else { + group.data = data; + } + group.size = data.length; + } + + setTotalAgents(total: number): void { + this.groups[AGENT_GROUP_KEY.All].size = total; + } + + generateOptions(): GroupOption[] { + const opts: GroupOption[] = []; + for (const key of this.groupOrder) { + const { label, size, groupType, data, color } = this.groups[key]; + if (size === 0) { + continue; + } + + switch (key) { + case AGENT_GROUP_KEY.All: + opts.push({ + label, + options: [ + { + label, + value: { groupType, size }, + color, + }, + ], + }); + break; + case AGENT_GROUP_KEY.Platform: + case AGENT_GROUP_KEY.Policy: + opts.push({ + label, + options: (data as Group[]).map(({ name, id, size: groupSize }) => ({ + label: name, + color: getColor(groupType), + value: { groupType, id, size: groupSize }, + })), + }); + break; + case AGENT_GROUP_KEY.Agent: + opts.push({ + label, + options: (data as Agent[]).map((agent: Agent) => ({ + label: agent.local_metadata.host.hostname, + key: agent.local_metadata.elastic.agent.id, + color, + value: { + groupType, + groups: { + policy: agent.policy_id ?? '', + platform: agent.local_metadata.os.platform, + }, + id: agent.local_metadata.elastic.agent.id, + online: agent.active, + }, + })), + }); + break; + } + } + return opts; + } +} diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index c7d8caee1a1bb..07351177e6eaf 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -5,30 +5,25 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; -import { EuiComboBox, EuiComboBoxOptionOption, EuiHealth, EuiHighlight } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { EuiComboBox, EuiHealth, EuiHighlight } from '@elastic/eui'; +import { useDebounce } from 'react-use'; import { useAllAgents } from './use_all_agents'; import { useAgentGroups } from './use_agent_groups'; import { useOsqueryPolicies } from './use_osquery_policies'; -import { Agent } from '../../common/shared_imports'; -import { - getNumAgentsInGrouping, - generateAgentCheck, - getNumOverlapped, - generateColorPicker, -} from './helpers'; +import { AgentGrouper } from './agent_grouper'; +import { getNumAgentsInGrouping, generateAgentCheck, getNumOverlapped } from './helpers'; -import { - ALL_AGENTS_LABEL, - AGENT_PLATFORMS_LABEL, - AGENT_POLICY_LABEL, - SELECT_AGENT_LABEL, - AGENT_SELECTION_LABEL, - generateSelectedAgentsMessage, -} from './translations'; +import { SELECT_AGENT_LABEL, generateSelectedAgentsMessage } from './translations'; -import { AGENT_GROUP_KEY, SelectedGroups, AgentOptionValue, GroupOptionValue, Group } from './types'; +import { + AGENT_GROUP_KEY, + SelectedGroups, + AgentOptionValue, + GroupOptionValue, + GroupOption, +} from './types'; export interface AgentsSelection { agents: string[]; @@ -42,81 +37,61 @@ interface AgentsTableProps { onChange: (payload: AgentsSelection) => void; } -type GroupOption = EuiComboBoxOptionOption; - -const getColor = generateColorPicker(); - -const generateOptions = (groupType: AGENT_GROUP_KEY, label: string, collection: Group[]) => { - return { - label, - options: collection.map(({ name, id, size }) => ({ - label: name, - color: getColor(groupType), - value: { groupType, id, size }, - })), - }; -} +const perPage = 10; const AgentsTableComponent: React.FC = ({ onChange }) => { + // search related + const [searchValue, setSearchValue] = useState(''); + const [modifyingSearch, setModifyingSearch] = useState(false); + const [page, setPage] = useState(1); + const [debouncedSearchValue, setDebouncedSearchValue] = useState(''); + useDebounce( + () => { + // reset the page, update the real search value, set the typing flag + setPage(1); + setDebouncedSearchValue(searchValue); + setModifyingSearch(false); + }, + 100, + [searchValue] + ); + + // grouping related const osqueryPolicyData = useOsqueryPolicies(); const { loading: groupsLoading, totalCount: totalNumAgents, groups } = useAgentGroups( osqueryPolicyData ); - const { agents } = useAllAgents(osqueryPolicyData); - const [loading, setLoading] = useState(true); + const grouper = useMemo(() => new AgentGrouper(), []); + const { agentsLoading, agents } = useAllAgents(osqueryPolicyData, debouncedSearchValue, { + perPage, + page, + }); + + // option related const [options, setOptions] = useState([]); + const [lastLabel, setLastLabel] = useState(''); const [selectedOptions, setSelectedOptions] = useState([]); const [numAgentsSelected, setNumAgentsSelected] = useState(0); useEffect(() => { - const allAgentsLabel = ALL_AGENTS_LABEL; - const opts: GroupOption[] = [ - { - label: allAgentsLabel, - options: [ - { - label: allAgentsLabel, - value: { groupType: AGENT_GROUP_KEY.All, size: totalNumAgents }, - color: getColor(AGENT_GROUP_KEY.All), - }, - ], - }, - ]; - - if (groups.platforms.length > 0) { - const groupType = AGENT_GROUP_KEY.Platform; - opts.push(generateOptions(groupType, AGENT_PLATFORMS_LABEL, groups.platforms)) - } - - if (groups.policies.length > 0) { - const groupType = AGENT_GROUP_KEY.Policy; - opts.push(generateOptions(groupType, AGENT_POLICY_LABEL, groups.policies)) - } - - if (agents && agents.length > 0) { - const groupType = AGENT_GROUP_KEY.Agent; - opts.push({ - label: AGENT_SELECTION_LABEL, - options: (agents as Agent[]).map((agent: Agent) => ({ - label: agent.local_metadata.host.hostname, - key: agent.local_metadata.elastic.agent.id, - color: getColor(groupType), - value: { - groupType, - groups: { policy: agent.policy_id ?? '', platform: agent.local_metadata.os.platform }, - id: agent.local_metadata.elastic.agent.id, - online: agent.active, - }, - })), - }); + // update the groups when groups or agents have changed + grouper.setTotalAgents(totalNumAgents); + grouper.updateGroup(AGENT_GROUP_KEY.Platform, groups.platforms); + grouper.updateGroup(AGENT_GROUP_KEY.Policy, groups.policies); + grouper.updateGroup(AGENT_GROUP_KEY.Agent, agents, page > 1); + const newOptions = grouper.generateOptions(); + setOptions(newOptions); + if (newOptions.length) { + const lastGroup = newOptions[newOptions.length - 1].options; + if (lastGroup?.length) { + setLastLabel(lastGroup[lastGroup.length - 1].label); + } } - setLoading(false); - setOptions(opts); - }, [groups.platforms, groups.policies, totalNumAgents, groupsLoading, agents]); + }, [groups.platforms, groups.policies, totalNumAgents, groupsLoading, agents, page, grouper]); const onSelection = useCallback( (selection: GroupOption[]) => { - // TODO?: optimize this by making it incremental + // TODO?: optimize this by making the selection computation incremental const newAgentSelection: AgentsSelection = { agents: [], allAgentsSelected: false, @@ -189,36 +164,49 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { [groups, onChange, totalNumAgents] ); - const renderOption = useCallback((option, searchValue, contentClassName) => { - const { label, value, key } = option; - return value?.groupType === AGENT_GROUP_KEY.Agent ? ( - + const renderOption = useCallback( + (option, searchVal, contentClassName) => { + const { label, value, key } = option; + if (label === lastLabel) { + setPage((p) => p + 1); + } + return value?.groupType === AGENT_GROUP_KEY.Agent ? ( + + + {label} +   + ({key}) + + + ) : ( - {label} + [{value?.size}]   - ({key}) + {label} +   + {value?.id && label !== value?.id && ({value?.id})} - - ) : ( - - [{value?.size}] -   - {label} -   - {value?.id && label !== value?.id && (({value?.id}))} - - ); + ); + }, + [lastLabel] + ); + + const onSearchChange = useCallback((v: string) => { + // set the typing flag and update the search value + setModifyingSearch(true); + setSearchValue(v); }, []); + return (
-

{SELECT_AGENT_LABEL}

{numAgentsSelected > 0 ? {generateSelectedAgentsMessage(numAgentsSelected)} : ''}   ; + interface BaseGroupOption { id?: string; groupType: AGENT_GROUP_KEY; diff --git a/x-pack/plugins/osquery/public/agents/use_agent_policies.ts b/x-pack/plugins/osquery/public/agents/use_agent_policies.ts index 056be8baafba9..3045423ccbe2d 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_policies.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_policies.ts @@ -7,7 +7,11 @@ import { useQueries, UseQueryResult } from 'react-query'; import { useKibana } from '../common/lib/kibana'; -import { AgentPolicy, agentPolicyRouteService, GetOneAgentPolicyResponse } from '../../../fleet/common'; +import { + AgentPolicy, + agentPolicyRouteService, + GetOneAgentPolicyResponse, +} from '../../../fleet/common'; export const useAgentPolicies = (policyIds: string[] = []) => { const { http } = useKibana().services; @@ -16,19 +20,19 @@ export const useAgentPolicies = (policyIds: string[] = []) => { policyIds.map((policyId) => ({ queryKey: ['agentPolicy', policyId], queryFn: () => http.get(agentPolicyRouteService.getInfoPath(policyId)), - options: {enabled: policyIds.length > 0} - })), + enabled: policyIds.length > 0, + })) ) as Array>; - const agentPoliciesLoading = agentResponse.some(p => p.isLoading) - const agentPolicies = agentResponse.map(p => p.data?.item) + const agentPoliciesLoading = agentResponse.some((p) => p.isLoading); + const agentPolicies = agentResponse.map((p) => p.data?.item); const agentPolicyById = agentPolicies.reduce((acc, p) => { if (!p) { - return acc + return acc; } - acc[p.id] = p - return acc - }, {} as {[key: string]: AgentPolicy}) + acc[p.id] = p; + return acc; + }, {} as { [key: string]: AgentPolicy }); return { agentPoliciesLoading, agentPolicies, agentPolicyById }; }; diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts index 607f9ae007692..54128e331e9c9 100644 --- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -14,16 +14,31 @@ interface UseAllAgents { osqueryPoliciesLoading: boolean; } -export const useAllAgents = ({ osqueryPolicies, osqueryPoliciesLoading }: UseAllAgents) => { - // TODO: properly fetch these in an async manner +interface RequestOptions { + perPage: number; + page: number; +} + +// TODO: break out the paginated vs all cases into separate hooks +export const useAllAgents = ( + { osqueryPolicies, osqueryPoliciesLoading }: UseAllAgents, + searchValue = '', + opts: RequestOptions = { perPage: 9000, page: 1 } +) => { + const { perPage, page } = opts; const { http } = useKibana().services; const { isLoading: agentsLoading, data: agentData } = useQuery( - ['agents', osqueryPolicies], + ['agents', osqueryPolicies, searchValue, page, perPage], async () => { + let kuery = `(${osqueryPolicies.map((p) => `policy_id:${p}`).join(' or ')})`; + if (searchValue) { + kuery += ` and local_metadata.host.hostname:*${searchValue}*`; + } return await http.get('/api/fleet/agents', { query: { - kuery: osqueryPolicies.map((p) => `policy_id:${p}`).join(' or '), - perPage: 9000, + kuery, + perPage, + page, }, }); }, diff --git a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts index d9f51033a14f4..f786e9167d2f8 100644 --- a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts +++ b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts @@ -15,13 +15,11 @@ export const useOsqueryPolicies = () => { const { isLoading: osqueryPoliciesLoading, data: osqueryPolicies } = useQuery( ['osqueryPolicies'], async () => { - const d = await http.get('/api/fleet/package_policies', { + return await http.get('/api/fleet/package_policies', { query: { kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:osquery_manager`, }, }); - console.log('init', d) - return d }, { select: (data) => data.items.map((p: { policy_id: string }) => p.policy_id) } ); From 8cf3186a267fbb78f16a1c14b53aa432899b8082 Mon Sep 17 00:00:00 2001 From: bryan Date: Fri, 16 Apr 2021 12:53:05 -0700 Subject: [PATCH 3/6] axed janky infinite scrolling, fixed agent fetching, added search on id --- .../osquery/public/agents/agent_grouper.ts | 5 +- .../osquery/public/agents/agents_table.tsx | 140 +++++------------- .../plugins/osquery/public/agents/helpers.ts | 63 +++++++- .../osquery/public/agents/translations.ts | 2 +- x-pack/plugins/osquery/public/agents/types.ts | 8 + .../osquery/public/agents/use_all_agents.ts | 13 +- .../live_query/form/agents_table_field.tsx | 5 +- 7 files changed, 121 insertions(+), 115 deletions(-) diff --git a/x-pack/plugins/osquery/public/agents/agent_grouper.ts b/x-pack/plugins/osquery/public/agents/agent_grouper.ts index da32a5621a002..419a3b9e733a4 100644 --- a/x-pack/plugins/osquery/public/agents/agent_grouper.ts +++ b/x-pack/plugins/osquery/public/agents/agent_grouper.ts @@ -85,7 +85,8 @@ export class AgentGrouper { opts.push({ label, options: (data as Group[]).map(({ name, id, size: groupSize }) => ({ - label: name, + label: name !== id ? `${name} (${id})` : name, + key: id, color: getColor(groupType), value: { groupType, id, size: groupSize }, })), @@ -95,7 +96,7 @@ export class AgentGrouper { opts.push({ label, options: (data as Agent[]).map((agent: Agent) => ({ - label: agent.local_metadata.host.hostname, + label: `${agent.local_metadata.host.hostname} (${agent.local_metadata.elastic.agent.id})`, key: agent.local_metadata.elastic.agent.id, color, value: { diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 07351177e6eaf..38132957c341f 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -13,7 +13,12 @@ import { useAllAgents } from './use_all_agents'; import { useAgentGroups } from './use_agent_groups'; import { useOsqueryPolicies } from './use_osquery_policies'; import { AgentGrouper } from './agent_grouper'; -import { getNumAgentsInGrouping, generateAgentCheck, getNumOverlapped } from './helpers'; +import { + getNumAgentsInGrouping, + generateAgentCheck, + getNumOverlapped, + generateAgentSelection, +} from './helpers'; import { SELECT_AGENT_LABEL, generateSelectedAgentsMessage } from './translations'; @@ -21,38 +26,30 @@ import { AGENT_GROUP_KEY, SelectedGroups, AgentOptionValue, - GroupOptionValue, GroupOption, + AgentSelection, } from './types'; -export interface AgentsSelection { - agents: string[]; - allAgentsSelected: boolean; - platformsSelected: string[]; - policiesSelected: string[]; -} - interface AgentsTableProps { - agentSelection: AgentsSelection; - onChange: (payload: AgentsSelection) => void; + agentSelection: AgentSelection; + onChange: (payload: AgentSelection) => void; } const perPage = 10; +const DEBOUNCE_DELAY = 100; // ms const AgentsTableComponent: React.FC = ({ onChange }) => { // search related const [searchValue, setSearchValue] = useState(''); const [modifyingSearch, setModifyingSearch] = useState(false); - const [page, setPage] = useState(1); const [debouncedSearchValue, setDebouncedSearchValue] = useState(''); useDebounce( () => { - // reset the page, update the real search value, set the typing flag - setPage(1); + // update the real search value, set the typing flag setDebouncedSearchValue(searchValue); setModifyingSearch(false); }, - 100, + DEBOUNCE_DELAY, [searchValue] ); @@ -64,12 +61,10 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { const grouper = useMemo(() => new AgentGrouper(), []); const { agentsLoading, agents } = useAllAgents(osqueryPolicyData, debouncedSearchValue, { perPage, - page, }); // option related const [options, setOptions] = useState([]); - const [lastLabel, setLastLabel] = useState(''); const [selectedOptions, setSelectedOptions] = useState([]); const [numAgentsSelected, setNumAgentsSelected] = useState(0); @@ -78,73 +73,23 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { grouper.setTotalAgents(totalNumAgents); grouper.updateGroup(AGENT_GROUP_KEY.Platform, groups.platforms); grouper.updateGroup(AGENT_GROUP_KEY.Policy, groups.policies); - grouper.updateGroup(AGENT_GROUP_KEY.Agent, agents, page > 1); + grouper.updateGroup(AGENT_GROUP_KEY.Agent, agents); const newOptions = grouper.generateOptions(); setOptions(newOptions); - if (newOptions.length) { - const lastGroup = newOptions[newOptions.length - 1].options; - if (lastGroup?.length) { - setLastLabel(lastGroup[lastGroup.length - 1].label); - } - } - }, [groups.platforms, groups.policies, totalNumAgents, groupsLoading, agents, page, grouper]); + }, [groups.platforms, groups.policies, totalNumAgents, groupsLoading, agents, grouper]); const onSelection = useCallback( (selection: GroupOption[]) => { // TODO?: optimize this by making the selection computation incremental - const newAgentSelection: AgentsSelection = { - agents: [], - allAgentsSelected: false, - platformsSelected: [], - policiesSelected: [], - }; - // parse through the selections to be able to determine how many are actually selected - const selectedAgents: AgentOptionValue[] = []; - const selectedGroups: SelectedGroups = { - policy: {}, - platform: {}, - }; - - // TODO: clean this up, make it less awkward - for (const opt of selection) { - const groupType = opt.value?.groupType; - let value; - switch (groupType) { - case AGENT_GROUP_KEY.All: - newAgentSelection.allAgentsSelected = true; - break; - case AGENT_GROUP_KEY.Platform: - value = opt.value as GroupOptionValue; - if (!newAgentSelection.allAgentsSelected) { - // we don't need to calculate diffs when all agents are selected - selectedGroups.platform[opt.value?.id ?? opt.label] = value.size; - } - newAgentSelection.platformsSelected.push(opt.label); - break; - case AGENT_GROUP_KEY.Policy: - value = opt.value as GroupOptionValue; - if (!newAgentSelection.allAgentsSelected) { - // we don't need to calculate diffs when all agents are selected - selectedGroups.policy[opt.value?.id ?? opt.label] = value.size; - } - newAgentSelection.policiesSelected.push(opt.label); - break; - case AGENT_GROUP_KEY.Agent: - value = opt.value as AgentOptionValue; - if (!newAgentSelection.allAgentsSelected) { - // we don't need to count how many agents are selected if they are all selected - selectedAgents.push(value); - } - if (value?.id) { - newAgentSelection.agents.push(value.id); - } - break; - default: - // this should never happen! - // eslint-disable-next-line no-console - console.error(`unknown group type ${groupType}`); - } - } + const { + newAgentSelection, + selectedAgents, + selectedGroups, + }: { + newAgentSelection: AgentSelection; + selectedAgents: AgentOptionValue[]; + selectedGroups: SelectedGroups; + } = generateAgentSelection(selection); if (newAgentSelection.allAgentsSelected) { setNumAgentsSelected(totalNumAgents); } else { @@ -164,36 +109,26 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { [groups, onChange, totalNumAgents] ); - const renderOption = useCallback( - (option, searchVal, contentClassName) => { - const { label, value, key } = option; - if (label === lastLabel) { - setPage((p) => p + 1); - } - return value?.groupType === AGENT_GROUP_KEY.Agent ? ( - - - {label} -   - ({key}) - - - ) : ( + const renderOption = useCallback((option, searchVal, contentClassName) => { + const { label, value } = option; + return value?.groupType === AGENT_GROUP_KEY.Agent ? ( + - [{value?.size}] -   {label} -   - {value?.id && label !== value?.id && ({value?.id})} - ); - }, - [lastLabel] - ); + + ) : ( + + [{value?.size ?? 0}] +   + {label} + + ); + }, []); const onSearchChange = useCallback((v: string) => { // set the typing flag and update the search value - setModifyingSearch(true); + setModifyingSearch(v !== ''); setSearchValue(v); }, []); @@ -205,6 +140,7 @@ const AgentsTableComponent: React.FC = ({ onChange }) => { placeholder={SELECT_AGENT_LABEL} isLoading={modifyingSearch || groupsLoading || agentsLoading} options={options} + isClearable={true} fullWidth={true} onSearchChange={onSearchChange} selectedOptions={selectedOptions} diff --git a/x-pack/plugins/osquery/public/agents/helpers.ts b/x-pack/plugins/osquery/public/agents/helpers.ts index 22817799da986..14a8dd64fb4da 100644 --- a/x-pack/plugins/osquery/public/agents/helpers.ts +++ b/x-pack/plugins/osquery/public/agents/helpers.ts @@ -20,6 +20,9 @@ import { Group, AgentOptionValue, AggregationDataPoint, + AgentSelection, + GroupOptionValue, + GroupOption, } from './types'; export type InspectResponse = Inspect & { response: string[] }; @@ -43,7 +46,8 @@ export const processAggregations = (aggs: Record) => { const platformTerms = aggs.platforms as TermsAggregate; const policyTerms = aggs.policies as TermsAggregate; - const policies = policyTerms?.buckets.map((o) => ({ name: o.key, id: o.key, size: o.doc_count })) ?? []; + const policies = + policyTerms?.buckets.map((o) => ({ name: o.key, id: o.key, size: o.doc_count })) ?? []; if (platformTerms?.buckets) { for (const { key, doc_count: size, policies: platformPolicies } of platformTerms.buckets) { @@ -96,6 +100,63 @@ export const generateAgentCheck = (selectedGroups: SelectedGroups) => { }; }; +export const generateAgentSelection = (selection: GroupOption[]) => { + const newAgentSelection: AgentSelection = { + agents: [], + allAgentsSelected: false, + platformsSelected: [], + policiesSelected: [], + }; + // parse through the selections to be able to determine how many are actually selected + const selectedAgents: AgentOptionValue[] = []; + const selectedGroups: SelectedGroups = { + policy: {}, + platform: {}, + }; + + // TODO: clean this up, make it less awkward + for (const opt of selection) { + const groupType = opt.value?.groupType; + let value; + switch (groupType) { + case AGENT_GROUP_KEY.All: + newAgentSelection.allAgentsSelected = true; + break; + case AGENT_GROUP_KEY.Platform: + value = opt.value as GroupOptionValue; + if (!newAgentSelection.allAgentsSelected) { + // we don't need to calculate diffs when all agents are selected + selectedGroups.platform[opt.value?.id ?? opt.label] = value.size; + } + newAgentSelection.platformsSelected.push(opt.label); + break; + case AGENT_GROUP_KEY.Policy: + value = opt.value as GroupOptionValue; + if (!newAgentSelection.allAgentsSelected) { + // we don't need to calculate diffs when all agents are selected + selectedGroups.policy[opt.value?.id ?? opt.label] = value.size; + } + newAgentSelection.policiesSelected.push(opt.label); + break; + case AGENT_GROUP_KEY.Agent: + value = opt.value as AgentOptionValue; + if (!newAgentSelection.allAgentsSelected) { + // we don't need to count how many agents are selected if they are all selected + selectedAgents.push(value); + } + if (value?.id) { + newAgentSelection.agents.push(value.id); + } + break; + default: + // this should never happen! + // eslint-disable-next-line no-console + console.error(`unknown group type ${groupType}`); + } + } + return { newAgentSelection, selectedGroups, selectedAgents }; +}; + export const generateTablePaginationOptions = ( activePage: number, limit: number, diff --git a/x-pack/plugins/osquery/public/agents/translations.ts b/x-pack/plugins/osquery/public/agents/translations.ts index af99a73d63de2..209761b4c8bdf 100644 --- a/x-pack/plugins/osquery/public/agents/translations.ts +++ b/x-pack/plugins/osquery/public/agents/translations.ts @@ -40,7 +40,7 @@ export const AGENT_SELECTION_LABEL = i18n.translate('xpack.osquery.agents.select }); export const SELECT_AGENT_LABEL = i18n.translate('xpack.osquery.agents.selectAgentLabel', { - defaultMessage: `Select Agents`, + defaultMessage: `Select agents or groups`, }); export const ERROR_ALL_AGENTS = i18n.translate('xpack.osquery.agents.errorSearchDescription', { diff --git a/x-pack/plugins/osquery/public/agents/types.ts b/x-pack/plugins/osquery/public/agents/types.ts index ee766537adb58..a47828ebe91b1 100644 --- a/x-pack/plugins/osquery/public/agents/types.ts +++ b/x-pack/plugins/osquery/public/agents/types.ts @@ -32,6 +32,14 @@ export interface SelectedGroups { export type GroupOption = EuiComboBoxOptionOption; +export interface AgentSelection { + agents: string[]; + allAgentsSelected: boolean; + platformsSelected: string[]; + policiesSelected: string[]; +} + + interface BaseGroupOption { id?: string; groupType: AGENT_GROUP_KEY; diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts index 54128e331e9c9..ee41558230152 100644 --- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -15,30 +15,29 @@ interface UseAllAgents { } interface RequestOptions { - perPage: number; - page: number; + perPage?: number; + page?: number; } // TODO: break out the paginated vs all cases into separate hooks export const useAllAgents = ( { osqueryPolicies, osqueryPoliciesLoading }: UseAllAgents, searchValue = '', - opts: RequestOptions = { perPage: 9000, page: 1 } + opts: RequestOptions = { perPage: 9000 } ) => { - const { perPage, page } = opts; + const { perPage } = opts; const { http } = useKibana().services; const { isLoading: agentsLoading, data: agentData } = useQuery( - ['agents', osqueryPolicies, searchValue, page, perPage], + ['agents', osqueryPolicies, searchValue, perPage], async () => { let kuery = `(${osqueryPolicies.map((p) => `policy_id:${p}`).join(' or ')})`; if (searchValue) { - kuery += ` and local_metadata.host.hostname:*${searchValue}*`; + kuery += ` and (local_metadata.host.hostname:/${searchValue}/ or local_metadata.elatic.agent.id:/${searchValue}/)`; } return await http.get('/api/fleet/agents', { query: { kuery, perPage, - page, }, }); }, 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 4bc9262af7613..ccde0fd8305f9 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,11 @@ import React, { useCallback } from 'react'; import { FieldHook } from '../../shared_imports'; -import { AgentsTable, AgentsSelection } from '../../agents/agents_table'; +import { AgentsTable } from '../../agents/agents_table'; +import { AgentSelection } from '../../agents/types'; interface AgentsTableFieldProps { - field: FieldHook; + field: FieldHook; } const AgentsTableFieldComponent: React.FC = ({ field }) => { From 5b88f79d0500ec3f7e9e0fb68ce8d8c6fb856e74 Mon Sep 17 00:00:00 2001 From: bryan Date: Fri, 16 Apr 2021 13:24:35 -0700 Subject: [PATCH 4/6] destroy extraneous white space --- x-pack/plugins/osquery/public/agents/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/osquery/public/agents/types.ts b/x-pack/plugins/osquery/public/agents/types.ts index a47828ebe91b1..b26404f9c5e70 100644 --- a/x-pack/plugins/osquery/public/agents/types.ts +++ b/x-pack/plugins/osquery/public/agents/types.ts @@ -39,7 +39,6 @@ export interface AgentSelection { policiesSelected: string[]; } - interface BaseGroupOption { id?: string; groupType: AGENT_GROUP_KEY; From bee973e0b5e0fb4e73290778817b812f99e1f99a Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sat, 17 Apr 2021 10:22:51 +0200 Subject: [PATCH 5/6] fix lint --- .../osquery/public/agents/helpers.test.ts | 6 ++++++ .../osquery/public/agents/use_agent_groups.ts | 20 ++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/osquery/public/agents/helpers.test.ts b/x-pack/plugins/osquery/public/agents/helpers.test.ts index 3efd1b877d1a0..f7ed4570b1a27 100644 --- a/x-pack/plugins/osquery/public/agents/helpers.test.ts +++ b/x-pack/plugins/osquery/public/agents/helpers.test.ts @@ -33,6 +33,7 @@ describe('processAggregations', () => { const { platforms, policies, overlap } = processAggregations(input); expect(platforms).toEqual([ { + id: 'darwin', name: 'darwin', size: 200, }, @@ -59,10 +60,12 @@ describe('processAggregations', () => { expect(platforms).toEqual([]); expect(policies).toEqual([ { + id: '8cd01a60-8a74-11eb-86cb-c58693443a4f', name: '8cd01a60-8a74-11eb-86cb-c58693443a4f', size: 100, }, { + id: '8cd06880-8a74-11eb-86cb-c58693443a4f', name: '8cd06880-8a74-11eb-86cb-c58693443a4f', size: 100, }, @@ -107,16 +110,19 @@ describe('processAggregations', () => { const { platforms, policies, overlap } = processAggregations(input); expect(platforms).toEqual([ { + id: 'darwin', name: 'darwin', size: 200, }, ]); expect(policies).toEqual([ { + id: '8cd01a60-8a74-11eb-86cb-c58693443a4f', name: '8cd01a60-8a74-11eb-86cb-c58693443a4f', size: 100, }, { + id: '8cd06880-8a74-11eb-86cb-c58693443a4f', name: '8cd06880-8a74-11eb-86cb-c58693443a4f', size: 100, }, 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 84115a5c78e7d..0853891f1919d 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -26,7 +26,7 @@ interface UseAgentGroups { export const useAgentGroups = ({ osqueryPolicies, osqueryPoliciesLoading }: UseAgentGroups) => { const { data } = useKibana().services; - const { agentPoliciesLoading, agentPolicyById } = useAgentPolicies(osqueryPolicies) + const { agentPoliciesLoading, agentPolicyById } = useAgentPolicies(osqueryPolicies); const [platforms, setPlatforms] = useState([]); const [policies, setPolicies] = useState([]); const [loading, setLoading] = useState(true); @@ -78,15 +78,17 @@ export const useAgentGroups = ({ osqueryPolicies, osqueryPoliciesLoading }: UseA policies: newPolicies, } = processAggregations(responseData.rawResponse.aggregations); - setPlatforms(newPlatforms) + setPlatforms(newPlatforms); setOverlap(newOverlap); - setPolicies(newPolicies.map(p => { - const name = agentPolicyById[p.id]?.name ?? p.name - return { - ...p, - name - } - })); + setPolicies( + newPolicies.map((p) => { + const name = agentPolicyById[p.id]?.name ?? p.name; + return { + ...p, + name, + }; + }) + ); } setLoading(false); From 6f6079a54a10a49e66a77df8772b949b4d40cf91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Sat, 17 Apr 2021 10:36:55 +0200 Subject: [PATCH 6/6] fix typo --- x-pack/plugins/osquery/public/agents/use_all_agents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts index ee41558230152..bd9b1c32412e6 100644 --- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -32,7 +32,7 @@ export const useAllAgents = ( async () => { let kuery = `(${osqueryPolicies.map((p) => `policy_id:${p}`).join(' or ')})`; if (searchValue) { - kuery += ` and (local_metadata.host.hostname:/${searchValue}/ or local_metadata.elatic.agent.id:/${searchValue}/)`; + kuery += ` and (local_metadata.host.hostname:/${searchValue}/ or local_metadata.elastic.agent.id:/${searchValue}/)`; } return await http.get('/api/fleet/agents', { query: {