diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx index d3b0e38a5e03..bf4c97d63d74 100644 --- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx @@ -8,15 +8,13 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { i18n } from '@kbn/i18n'; -import { EuiLink, EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui'; +import { EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { PLUGIN_ID } from '../../../fleet/common'; -import { pagePathGetters } from '../../../fleet/public'; +import { AgentIdToName } from '../agents/agent_id_to_name'; import { useActionResults } from './use_action_results'; import { useAllResults } from '../results/use_all_results'; import { Direction } from '../../common/search_strategy'; -import { useKibana } from '../common/lib/kibana'; interface ActionResultsSummaryProps { actionId: string; @@ -35,7 +33,6 @@ const ActionResultsSummaryComponent: React.FC = ({ expirationDate, agentIds, }) => { - const getUrlForApp = useKibana().services.application.getUrlForApp; // @ts-expect-error update types const [pageIndex, setPageIndex] = useState(0); // @ts-expect-error update types @@ -70,20 +67,7 @@ const ActionResultsSummaryComponent: React.FC = ({ isLive, }); - const renderAgentIdColumn = useCallback( - (agentId) => ( - - {agentId} - - ), - [getUrlForApp] - ); + const renderAgentIdColumn = useCallback((agentId) => , []); const renderRowsColumn = useCallback( (_, item) => { diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx index 0ee928ad8aa1..045c1f67b070 100644 --- a/x-pack/plugins/osquery/public/actions/actions_table.tsx +++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx @@ -9,6 +9,7 @@ import { isArray } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiButtonIcon, EuiCodeBlock, formatDate } from '@elastic/eui'; import React, { useState, useCallback, useMemo } from 'react'; +import { useHistory } from 'react-router-dom'; import { useAllActions } from './use_all_actions'; import { Direction } from '../../common/search_strategy'; @@ -27,6 +28,7 @@ const ActionTableResultsButton = React.memo(({ ac ActionTableResultsButton.displayName = 'ActionTableResultsButton'; const ActionsTableComponent = () => { + const { push } = useHistory(); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(20); @@ -67,6 +69,16 @@ const ActionsTableComponent = () => { [] ); + const handlePlayClick = useCallback( + (item) => + push('/live_queries/new', { + form: { + query: item._source?.data?.query, + }, + }), + [push] + ); + const columns = useMemo( () => [ { @@ -106,6 +118,11 @@ const ActionsTableComponent = () => { defaultMessage: 'View details', }), actions: [ + { + type: 'icon', + icon: 'play', + onClick: handlePlayClick, + }, { render: renderActionsColumn, }, @@ -113,6 +130,7 @@ const ActionsTableComponent = () => { }, ], [ + handlePlayClick, renderActionsColumn, renderAgentsColumn, renderCreatedByColumn, @@ -135,6 +153,7 @@ const ActionsTableComponent = () => { = ({ agentId }) => { + const getUrlForApp = useKibana().services.application.getUrlForApp; + const { data } = useAgentDetails({ agentId }); + + return ( + + {data?.item.local_metadata.host.name ?? agentId} + + ); +}; + +export const AgentIdToName = React.memo(AgentIdToNameComponent); diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 7e8f49c05161..53e2ce1d5342 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -21,7 +21,12 @@ import { generateAgentSelection, } from './helpers'; -import { SELECT_AGENT_LABEL, generateSelectedAgentsMessage } from './translations'; +import { + SELECT_AGENT_LABEL, + generateSelectedAgentsMessage, + ALL_AGENTS_LABEL, + AGENT_POLICY_LABEL, +} from './translations'; import { AGENT_GROUP_KEY, @@ -72,8 +77,17 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh useEffect(() => { if (agentSelection && !defaultValueInitialized.current && options.length) { - if (agentSelection.policiesSelected) { - const policyOptions = find(['label', 'Policy'], options); + if (agentSelection.allAgentsSelected) { + const allAgentsOptions = find(['label', ALL_AGENTS_LABEL], options); + + if (allAgentsOptions?.options) { + setSelectedOptions(allAgentsOptions.options); + defaultValueInitialized.current = true; + } + } + + if (agentSelection.policiesSelected.length) { + const policyOptions = find(['label', AGENT_POLICY_LABEL], options); if (policyOptions) { const defaultOptions = policyOptions.options?.filter((option) => @@ -82,12 +96,12 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh if (defaultOptions?.length) { setSelectedOptions(defaultOptions); + defaultValueInitialized.current = true; } - defaultValueInitialized.current = true; } } } - }, [agentSelection, options]); + }, [agentSelection, options, selectedOptions]); useEffect(() => { // update the groups when groups or agents have changed diff --git a/x-pack/plugins/osquery/public/agents/use_agent_details.ts b/x-pack/plugins/osquery/public/agents/use_agent_details.ts new file mode 100644 index 000000000000..1a0663812dec --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/use_agent_details.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useQuery } from 'react-query'; + +import { GetOneAgentResponse, agentRouteService } from '../../../fleet/common'; +import { useErrorToast } from '../common/hooks/use_error_toast'; +import { useKibana } from '../common/lib/kibana'; + +interface UseAgentDetails { + agentId: string; +} + +export const useAgentDetails = ({ agentId }: UseAgentDetails) => { + const { http } = useKibana().services; + const setErrorToast = useErrorToast(); + return useQuery( + ['agentDetails', agentId], + () => http.get(agentRouteService.getInfoPath(agentId)), + { + enabled: agentId.length > 0, + onSuccess: () => setErrorToast(), + onError: (error) => + setErrorToast(error as Error, { + title: i18n.translate('xpack.osquery.agentDetails.fetchError', { + defaultMessage: 'Error while fetching agent details', + }), + }), + } + ); +}; 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 30ba4d2f5790..cda15cc80543 100644 --- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -38,7 +38,7 @@ export const useAllAgents = ( let kuery = `last_checkin_status: online and (${policyFragment})`; if (searchValue) { - kuery += `and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`; + kuery += ` and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`; } return http.get(agentRouteService.getListPath(), { diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 8654a74fecfb..658280042696 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -110,7 +110,7 @@ const LiveQueryFormComponent: React.FC = ({ { agentSelection: { agents: [], - allAgentsSelected: false, + allAgentsSelected: true, platformsSelected: [], policiesSelected: [], }, diff --git a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx index 9967eb97cddf..40614c1f3e1b 100644 --- a/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx +++ b/x-pack/plugins/osquery/public/routes/live_queries/new/index.tsx @@ -8,7 +8,7 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import qs from 'query-string'; import { WithHeaderLayout } from '../../../components/layouts'; @@ -19,12 +19,18 @@ import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge'; const NewLiveQueryPageComponent = () => { useBreadcrumbs('live_query_new'); + const { replace } = useHistory(); const location = useLocation(); const liveQueryListProps = useRouterNavigate('live_queries'); const formDefaultValue = useMemo(() => { const queryParams = qs.parse(location.search); + if (location.state?.form.query) { + replace({ state: null }); + return { query: location.state?.form.query }; + } + if (queryParams?.agentPolicyId) { return { agentSelection: { @@ -37,7 +43,7 @@ const NewLiveQueryPageComponent = () => { } return undefined; - }, [location.search]); + }, [location.search, location.state, replace]); const LeftColumn = useMemo( () => ( diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx index 7e8e8e543dfa..8738c06d0659 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx @@ -16,6 +16,7 @@ import { import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useHistory } from 'react-router-dom'; import { SavedObject } from 'kibana/public'; import { WithHeaderLayout } from '../../../components/layouts'; @@ -51,6 +52,7 @@ const EditButton = React.memo(EditButtonComponent); const SavedQueriesPageComponent = () => { useBreadcrumbs('saved_queries'); + const { push } = useHistory(); const newQueryLinkProps = useRouterNavigate('saved_queries/new'); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -59,21 +61,15 @@ const SavedQueriesPageComponent = () => { const { data } = useSavedQueries({ isLive: true }); - // const handlePlayClick = useCallback( - // (item) => - // push({ - // search: qs.stringify({ - // tab: 'live_query', - // }), - // state: { - // query: { - // id: item.id, - // query: item.attributes.query, - // }, - // }, - // }), - // [push] - // ); + const handlePlayClick = useCallback( + (item) => + push('/live_queries/new', { + form: { + savedQueryId: item.id, + }, + }), + [push] + ); const renderEditAction = useCallback( (item: SavedObject<{ name: string }>) => ( @@ -96,45 +92,53 @@ const SavedQueriesPageComponent = () => { () => [ { field: 'attributes.id', - name: 'Query ID', + name: i18n.translate('xpack.osquery.savedQueries.table.queryIdColumnTitle', { + defaultMessage: 'Query ID', + }), sortable: true, truncateText: true, }, { field: 'attributes.description', - name: 'Description', + name: i18n.translate('xpack.osquery.savedQueries.table.descriptionColumnTitle', { + defaultMessage: 'Description', + }), sortable: true, truncateText: true, }, { field: 'attributes.created_by', - name: 'Created by', + name: i18n.translate('xpack.osquery.savedQueries.table.createdByColumnTitle', { + defaultMessage: 'Created by', + }), sortable: true, truncateText: true, }, { field: 'attributes.updated_at', - name: 'Last updated at', + name: i18n.translate('xpack.osquery.savedQueries.table.updatedAtColumnTitle', { + defaultMessage: 'Last updated at', + }), sortable: (item: SavedObject<{ updated_at: string }>) => item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0, truncateText: true, render: renderUpdatedAt, }, { - name: 'Actions', + name: i18n.translate('xpack.osquery.savedQueries.table.actionsColumnTitle', { + defaultMessage: 'Actions', + }), actions: [ - // { - // name: 'Live query', - // description: 'Run live query', - // type: 'icon', - // icon: 'play', - // onClick: handlePlayClick, - // }, + { + type: 'icon', + icon: 'play', + onClick: handlePlayClick, + }, { render: renderEditAction }, ], }, ], - [renderEditAction, renderUpdatedAt] + [handlePlayClick, renderEditAction, renderUpdatedAt] ); const onTableChange = useCallback(({ page = {}, sort = {} }) => { diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx index e30954a695b2..073b56bfd1d4 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx @@ -7,10 +7,11 @@ import { find } from 'lodash/fp'; import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiText } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { SimpleSavedObject } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useHistory, useLocation } from 'react-router-dom'; import { useSavedQueries } from './use_saved_queries'; @@ -29,19 +30,25 @@ const SavedQueriesDropdownComponent: React.FC = ({ disabled, onChange, }) => { + const { replace } = useHistory(); + const location = useLocation(); const [selectedOptions, setSelectedOptions] = useState([]); const { data } = useSavedQueries({}); - const queryOptions = - data?.savedObjects?.map((savedQuery) => ({ - label: savedQuery.attributes.id ?? '', - value: { - id: savedQuery.attributes.id, - description: savedQuery.attributes.description, - query: savedQuery.attributes.query, - }, - })) ?? []; + const queryOptions = useMemo( + () => + data?.savedObjects?.map((savedQuery) => ({ + label: savedQuery.attributes.id ?? '', + value: { + savedObjectId: savedQuery.id, + id: savedQuery.attributes.id, + description: savedQuery.attributes.description, + query: savedQuery.attributes.query, + }, + })) ?? [], + [data?.savedObjects] + ); const handleSavedQueryChange = useCallback( (newSelectedOptions) => { @@ -73,6 +80,20 @@ const SavedQueriesDropdownComponent: React.FC = ({ [] ); + useEffect(() => { + const savedQueryId = location.state?.form?.savedQueryId; + + if (savedQueryId) { + const savedQueryOption = find(['value.savedObjectId', savedQueryId], queryOptions); + + if (savedQueryOption) { + handleSavedQueryChange([savedQueryOption]); + } + + replace({ state: null }); + } + }, [handleSavedQueryChange, replace, location.state, queryOptions]); + return (