[Osquery] Fix 7.14 live query history view (#105211)

This commit is contained in:
Patryk Kopyciński 2021-07-12 18:55:06 +03:00 committed by GitHub
parent 0c9777c602
commit 3e5ed77470
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 207 additions and 71 deletions

View file

@ -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<ActionResultsSummaryProps> = ({
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<ActionResultsSummaryProps> = ({
isLive,
});
const renderAgentIdColumn = useCallback(
(agentId) => (
<EuiLink
className="eui-textTruncate"
href={getUrlForApp(PLUGIN_ID, {
path: `#` + pagePathGetters.agent_details({ agentId })[1],
})}
target="_blank"
>
{agentId}
</EuiLink>
),
[getUrlForApp]
);
const renderAgentIdColumn = useCallback((agentId) => <AgentIdToName agentId={agentId} />, []);
const renderRowsColumn = useCallback(
(_, item) => {

View file

@ -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<ActionTableResultsButtonProps>(({ 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 = () => {
<EuiBasicTable
// eslint-disable-next-line react-perf/jsx-no-new-array-as-prop
items={actionsData?.actions ?? []}
// @ts-expect-error update types
columns={columns}
pagination={pagination}
onChange={onTableChange}

View file

@ -0,0 +1,37 @@
/*
* 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 { EuiLink } from '@elastic/eui';
import React from 'react';
import { useAgentDetails } from './use_agent_details';
import { PLUGIN_ID } from '../../../fleet/common';
import { pagePathGetters } from '../../../fleet/public';
import { useKibana } from '../common/lib/kibana';
interface AgentIdToNameProps {
agentId: string;
}
const AgentIdToNameComponent: React.FC<AgentIdToNameProps> = ({ agentId }) => {
const getUrlForApp = useKibana().services.application.getUrlForApp;
const { data } = useAgentDetails({ agentId });
return (
<EuiLink
className="eui-textTruncate"
href={getUrlForApp(PLUGIN_ID, {
path: `#` + pagePathGetters.agent_details({ agentId })[1],
})}
target="_blank"
>
{data?.item.local_metadata.host.name ?? agentId}
</EuiLink>
);
};
export const AgentIdToName = React.memo(AgentIdToNameComponent);

View file

@ -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<AgentsTableProps> = ({ 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<AgentsTableProps> = ({ 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

View file

@ -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<GetOneAgentResponse>(
['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',
}),
}),
}
);
};

View file

@ -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(), {

View file

@ -110,7 +110,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
{
agentSelection: {
agents: [],
allAgentsSelected: false,
allAgentsSelected: true,
platformsSelected: [],
policiesSelected: [],
},

View file

@ -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(
() => (

View file

@ -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 = {} }) => {

View file

@ -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<SavedQueriesDropdownProps> = ({
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<SavedQueriesDropdownProps> = ({
[]
);
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 (
<EuiFormRow
label={

View file

@ -258,7 +258,7 @@ const ViewResultsInDiscoverActionComponent: React.FC<ViewResultsInDiscoverAction
}
: {
to: 'now',
from: 'now-15m',
from: 'now-1d',
mode: 'relative',
},
});

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import type { estypes } from '@elastic/elasticsearch';
import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common';
import { AgentsRequestOptions } from '../../../../../../common/search_strategy';
// import { createQueryFilterClauses } from '../../../../../../common/utils/build_query';
@ -24,10 +26,23 @@ export const buildActionsQuery = ({
body: {
// query: { bool: { filter } },
query: {
term: {
type: {
value: 'INPUT_ACTION',
},
bool: {
must: [
{
term: {
type: {
value: 'INPUT_ACTION',
},
},
},
{
term: {
input_type: {
value: 'osquery',
},
},
},
] as estypes.QueryDslQueryContainer[],
},
},
from: cursorStart,