[Osquery] Fix Live query form errors handling (#99015)
This commit is contained in:
parent
85b78711c6
commit
3f39f5e275
|
@ -8,7 +8,7 @@
|
|||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
import { Inspect, Maybe, PageInfoPaginated } from '../../common';
|
||||
import { Inspect, Maybe, PageInfoPaginated, SortField } from '../../common';
|
||||
import { RequestOptionsPaginated } from '../..';
|
||||
|
||||
export type ResultEdges = estypes.SearchResponse<unknown>['hits']['hits'];
|
||||
|
@ -20,7 +20,8 @@ export interface ResultsStrategyResponse extends IEsSearchResponse {
|
|||
inspect?: Maybe<Inspect>;
|
||||
}
|
||||
|
||||
export interface ResultsRequestOptions extends RequestOptionsPaginated {
|
||||
export interface ResultsRequestOptions extends Omit<RequestOptionsPaginated, 'sort'> {
|
||||
actionId: string;
|
||||
agentId?: string;
|
||||
sort: SortField[];
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"id": "osquery",
|
||||
"kibanaVersion": "kibana",
|
||||
"optionalPlugins": [
|
||||
"home"
|
||||
"home",
|
||||
"lens"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"esUiShared",
|
||||
|
|
|
@ -18,8 +18,10 @@ import {
|
|||
EuiDescriptionList,
|
||||
EuiInMemoryTable,
|
||||
EuiCodeBlock,
|
||||
EuiProgress,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { pagePathGetters } from '../../../fleet/public';
|
||||
import { useActionResults } from './use_action_results';
|
||||
|
@ -27,6 +29,10 @@ import { useAllResults } from '../results/use_all_results';
|
|||
import { Direction } from '../../common/search_strategy';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
const StyledEuiCard = styled(EuiCard)`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
interface ActionResultsSummaryProps {
|
||||
actionId: string;
|
||||
agentIds?: string[];
|
||||
|
@ -66,8 +72,12 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
|
|||
actionId,
|
||||
activePage: pageIndex,
|
||||
limit: pageSize,
|
||||
direction: Direction.asc,
|
||||
sortField: '@timestamp',
|
||||
sort: [
|
||||
{
|
||||
field: '@timestamp',
|
||||
direction: Direction.asc,
|
||||
},
|
||||
],
|
||||
isLive,
|
||||
});
|
||||
|
||||
|
@ -215,14 +225,15 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
|
|||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCard title="" description="" textAlign="left">
|
||||
<StyledEuiCard title="" description="" textAlign="left">
|
||||
{notRespondedCount ? <EuiProgress size="xs" position="absolute" /> : null}
|
||||
<EuiDescriptionList
|
||||
compressed
|
||||
textStyle="reverse"
|
||||
type="responsiveColumn"
|
||||
listItems={listItems}
|
||||
/>
|
||||
</EuiCard>
|
||||
</StyledEuiCard>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ import {
|
|||
ResultEdges,
|
||||
PageInfoPaginated,
|
||||
OsqueryQueries,
|
||||
ResultsRequestOptions,
|
||||
ResultsStrategyResponse,
|
||||
ActionResultsRequestOptions,
|
||||
ActionResultsStrategyResponse,
|
||||
Direction,
|
||||
} from '../../common/search_strategy';
|
||||
import { ESTermQuery } from '../../common/typed_json';
|
||||
|
@ -65,7 +65,7 @@ export const useActionResults = ({
|
|||
['actionResults', { actionId }],
|
||||
async () => {
|
||||
const responseData = await data.search
|
||||
.search<ResultsRequestOptions, ResultsStrategyResponse>(
|
||||
.search<ActionResultsRequestOptions, ActionResultsStrategyResponse>(
|
||||
{
|
||||
actionId,
|
||||
factoryQueryType: OsqueryQueries.actionResults,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { filter } from 'lodash/fp';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -45,7 +45,8 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
|
|||
application: { getUrlForApp },
|
||||
http,
|
||||
} = useKibana().services;
|
||||
const { replace } = useHistory();
|
||||
const { state: locationState } = useLocation();
|
||||
const { replace, go } = useHistory();
|
||||
|
||||
const agentsLinkHref = useMemo(() => {
|
||||
if (!policy?.policy_id) return '#';
|
||||
|
@ -93,6 +94,16 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
|
|||
}
|
||||
}, [editMode, http, policy?.policy_id, policyAgentsCount]);
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
in order to enable Osquery side nav we need to refresh the whole Kibana
|
||||
TODO: Find a better solution
|
||||
*/
|
||||
if (editMode && locationState?.forceRefresh) {
|
||||
go(0);
|
||||
}
|
||||
}, [editMode, go, locationState]);
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
by default Fleet set up streams with an empty scheduled query,
|
||||
|
@ -124,6 +135,9 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
|
|||
pagePathGetters.integration_policy_edit({
|
||||
packagePolicyId: newPackagePolicy.id,
|
||||
}),
|
||||
state: {
|
||||
forceRefresh: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
} as CreatePackagePolicyRouteState,
|
||||
|
|
|
@ -35,7 +35,10 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
// onSubmit,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { http } = useKibana().services;
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const {
|
||||
data,
|
||||
|
@ -51,6 +54,10 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
}),
|
||||
{
|
||||
onSuccess,
|
||||
onError: (error) => {
|
||||
// @ts-expect-error update types
|
||||
toasts.addError(error, { title: error.body.error, toastMessage: error.body.message });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -15,9 +15,17 @@ interface ResultTabsProps {
|
|||
actionId: string;
|
||||
agentIds?: string[];
|
||||
isLive?: boolean;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
const ResultTabsComponent: React.FC<ResultTabsProps> = ({ actionId, agentIds, isLive }) => {
|
||||
const ResultTabsComponent: React.FC<ResultTabsProps> = ({
|
||||
actionId,
|
||||
agentIds,
|
||||
endDate,
|
||||
isLive,
|
||||
startDate,
|
||||
}) => {
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -36,12 +44,18 @@ const ResultTabsComponent: React.FC<ResultTabsProps> = ({ actionId, agentIds, is
|
|||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ResultsTable actionId={actionId} agentIds={agentIds} isLive={isLive} />
|
||||
<ResultsTable
|
||||
actionId={actionId}
|
||||
agentIds={agentIds}
|
||||
isLive={isLive}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
[actionId, agentIds, isLive]
|
||||
[actionId, agentIds, endDate, isLive, startDate]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -7,15 +7,13 @@
|
|||
|
||||
import { isEmpty, isEqual, keys, map } from 'lodash/fp';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiDataGrid,
|
||||
EuiDataGridSorting,
|
||||
EuiDataGridProps,
|
||||
EuiDataGridColumn,
|
||||
EuiLink,
|
||||
EuiTextColor,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiSpacer,
|
||||
EuiLoadingContent,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
|
||||
|
@ -26,6 +24,11 @@ import { Direction, ResultEdges } from '../../common/search_strategy';
|
|||
import { useKibana } from '../common/lib/kibana';
|
||||
import { useActionResults } from '../action_results/use_action_results';
|
||||
import { generateEmptyDataMessage } from './translations';
|
||||
import {
|
||||
ViewResultsInDiscoverAction,
|
||||
ViewResultsInLensAction,
|
||||
ViewResultsActionButtonType,
|
||||
} from '../scheduled_query_groups/scheduled_query_group_queries_table';
|
||||
|
||||
const DataContext = createContext<ResultEdges>([]);
|
||||
|
||||
|
@ -33,20 +36,17 @@ interface ResultsTableComponentProps {
|
|||
actionId: string;
|
||||
selectedAgent?: string;
|
||||
agentIds?: string[];
|
||||
endDate?: string;
|
||||
isLive?: boolean;
|
||||
}
|
||||
|
||||
interface SummaryTableValue {
|
||||
total: number | string;
|
||||
pending: number | string;
|
||||
responded: number;
|
||||
failed: number;
|
||||
startDate?: string;
|
||||
}
|
||||
|
||||
const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
||||
actionId,
|
||||
agentIds,
|
||||
isLive,
|
||||
startDate,
|
||||
endDate,
|
||||
}) => {
|
||||
const {
|
||||
// @ts-expect-error update types
|
||||
|
@ -61,52 +61,6 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
isLive,
|
||||
});
|
||||
|
||||
const notRespondedCount = useMemo(() => {
|
||||
if (!agentIds || !aggregations.totalResponded) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return agentIds.length - aggregations.totalResponded;
|
||||
}, [aggregations.totalResponded, agentIds]);
|
||||
|
||||
const summaryColumns: Array<EuiBasicTableColumn<SummaryTableValue>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
field: 'total',
|
||||
name: 'Agents queried',
|
||||
},
|
||||
{
|
||||
field: 'responded',
|
||||
name: 'Successful',
|
||||
},
|
||||
{
|
||||
field: 'pending',
|
||||
name: 'Not yet responded',
|
||||
},
|
||||
{
|
||||
field: 'failed',
|
||||
name: 'Failed',
|
||||
// eslint-disable-next-line react/display-name
|
||||
render: (failed: number) => (
|
||||
<EuiTextColor color={failed ? 'danger' : 'default'}>{failed}</EuiTextColor>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const summaryItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
total: agentIds?.length ?? '-',
|
||||
pending: notRespondedCount,
|
||||
responded: aggregations.totalResponded,
|
||||
failed: aggregations.failed,
|
||||
},
|
||||
],
|
||||
[aggregations, agentIds, notRespondedCount]
|
||||
);
|
||||
|
||||
const { getUrlForApp } = useKibana().services.application;
|
||||
|
||||
const getFleetAppUrl = useCallback(
|
||||
|
@ -132,17 +86,23 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
[setPagination]
|
||||
);
|
||||
|
||||
const [sortingColumns, setSortingColumns] = useState<EuiDataGridSorting['columns']>([
|
||||
{
|
||||
id: 'agent.name',
|
||||
direction: Direction.asc,
|
||||
},
|
||||
]);
|
||||
const [columns, setColumns] = useState<EuiDataGridColumn[]>([]);
|
||||
|
||||
const [sortingColumns, setSortingColumns] = useState<EuiDataGridSorting['columns']>([]);
|
||||
|
||||
const { data: allResultsData } = useAllResults({
|
||||
const { data: allResultsData, isFetched } = useAllResults({
|
||||
actionId,
|
||||
activePage: pagination.pageIndex,
|
||||
limit: pagination.pageSize,
|
||||
direction: Direction.asc,
|
||||
sortField: '@timestamp',
|
||||
isLive,
|
||||
sort: sortingColumns.map((sortedColumn) => ({
|
||||
field: sortedColumn.id,
|
||||
direction: sortedColumn.direction as Direction,
|
||||
})),
|
||||
});
|
||||
|
||||
const [visibleColumns, setVisibleColumns] = useState<string[]>([]);
|
||||
|
@ -234,27 +194,50 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
}
|
||||
}, [columns, allResultsData?.edges]);
|
||||
|
||||
const toolbarVisibility = useMemo(
|
||||
() => ({
|
||||
additionalControls: (
|
||||
<>
|
||||
<ViewResultsInDiscoverAction
|
||||
actionId={actionId}
|
||||
buttonType={ViewResultsActionButtonType.button}
|
||||
endDate={endDate}
|
||||
startDate={startDate}
|
||||
/>
|
||||
<ViewResultsInLensAction
|
||||
actionId={actionId}
|
||||
buttonType={ViewResultsActionButtonType.button}
|
||||
endDate={endDate}
|
||||
startDate={startDate}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
}),
|
||||
[actionId, endDate, startDate]
|
||||
);
|
||||
|
||||
if (!aggregations.totalResponded) {
|
||||
return <EuiLoadingContent lines={5} />;
|
||||
}
|
||||
|
||||
if (aggregations.totalResponded && isFetched && !allResultsData?.edges.length) {
|
||||
return <EuiCallOut title={generateEmptyDataMessage(aggregations.totalResponded)} />;
|
||||
}
|
||||
|
||||
return (
|
||||
// @ts-expect-error update types
|
||||
<DataContext.Provider value={allResultsData?.edges}>
|
||||
<EuiBasicTable items={summaryItems} rowHeader="total" columns={summaryColumns} />
|
||||
<EuiSpacer />
|
||||
{columns.length > 0 ? (
|
||||
<EuiDataGrid
|
||||
aria-label="Osquery results"
|
||||
columns={columns}
|
||||
columnVisibility={columnVisibility}
|
||||
rowCount={allResultsData?.totalCount ?? 0}
|
||||
renderCellValue={renderCellValue}
|
||||
sorting={tableSorting}
|
||||
pagination={tablePagination}
|
||||
height="500px"
|
||||
/>
|
||||
) : (
|
||||
<div className={'eui-textCenter'}>
|
||||
{generateEmptyDataMessage(aggregations.totalResponded)}
|
||||
</div>
|
||||
)}
|
||||
<EuiDataGrid
|
||||
aria-label="Osquery results"
|
||||
columns={columns}
|
||||
columnVisibility={columnVisibility}
|
||||
rowCount={allResultsData?.totalCount ?? 0}
|
||||
renderCellValue={renderCellValue}
|
||||
sorting={tableSorting}
|
||||
pagination={tablePagination}
|
||||
height="500px"
|
||||
toolbarVisibility={toolbarVisibility}
|
||||
/>
|
||||
</DataContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -34,9 +34,8 @@ export interface ResultsArgs {
|
|||
interface UseAllResults {
|
||||
actionId: string;
|
||||
activePage: number;
|
||||
direction: Direction;
|
||||
limit: number;
|
||||
sortField: string;
|
||||
sort: Array<{ field: string; direction: Direction }>;
|
||||
filterQuery?: ESTermQuery | string;
|
||||
skip?: boolean;
|
||||
isLive?: boolean;
|
||||
|
@ -45,9 +44,8 @@ interface UseAllResults {
|
|||
export const useAllResults = ({
|
||||
actionId,
|
||||
activePage,
|
||||
direction,
|
||||
limit,
|
||||
sortField,
|
||||
sort,
|
||||
filterQuery,
|
||||
skip = false,
|
||||
isLive = false,
|
||||
|
@ -58,7 +56,7 @@ export const useAllResults = ({
|
|||
} = useKibana().services;
|
||||
|
||||
return useQuery(
|
||||
['allActionResults', { actionId, activePage, direction, limit, sortField }],
|
||||
['allActionResults', { actionId, activePage, limit, sort }],
|
||||
async () => {
|
||||
const responseData = await data.search
|
||||
.search<ResultsRequestOptions, ResultsStrategyResponse>(
|
||||
|
@ -67,10 +65,7 @@ export const useAllResults = ({
|
|||
factoryQueryType: OsqueryQueries.results,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort: {
|
||||
direction,
|
||||
field: sortField,
|
||||
},
|
||||
sort,
|
||||
},
|
||||
{
|
||||
strategy: 'osquerySearchStrategy',
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useDiscoverLink } from '../../../common/hooks';
|
||||
interface LiveQueryDetailsActionsMenuProps {
|
||||
actionId: string;
|
||||
}
|
||||
|
||||
const LiveQueryDetailsActionsMenuComponent: React.FC<LiveQueryDetailsActionsMenuProps> = ({
|
||||
actionId,
|
||||
}) => {
|
||||
const discoverLinkProps = useDiscoverLink({ filters: [{ key: 'action_id', value: actionId }] });
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
setPopover((currentIsPopoverOpen) => !currentIsPopoverOpen);
|
||||
}, []);
|
||||
|
||||
const closePopover = useCallback(() => {
|
||||
setPopover(false);
|
||||
}, []);
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
<EuiContextMenuItem key="copy" icon="copy" {...discoverLinkProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.liveQueryResults.viewResultsInDiscoverLabel"
|
||||
defaultMessage="View results in Discover"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
],
|
||||
[discoverLinkProps]
|
||||
);
|
||||
|
||||
const button = useMemo(
|
||||
() => (
|
||||
<EuiButton iconType="arrowDown" iconSide="right" onClick={onButtonClick}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.liveQueryResults.actionsMenuButtonLabel"
|
||||
defaultMessage="Actions"
|
||||
/>
|
||||
</EuiButton>
|
||||
),
|
||||
[onButtonClick]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="liveQueryDetailsActionsMenu"
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenuPanel size="s" items={items} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export const LiveQueryDetailsActionsMenu = React.memo(LiveQueryDetailsActionsMenuComponent);
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiTextColor,
|
||||
|
@ -27,7 +28,6 @@ import { WithHeaderLayout } from '../../../components/layouts';
|
|||
import { useActionResults } from '../../../action_results/use_action_results';
|
||||
import { useActionDetails } from '../../../actions/use_action_details';
|
||||
import { ResultTabs } from '../../../queries/edit/tabs';
|
||||
import { LiveQueryDetailsActionsMenu } from './actions_menu';
|
||||
import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs';
|
||||
import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
|
||||
|
||||
|
@ -120,15 +120,9 @@ const LiveQueryDetailsPageComponent = () => {
|
|||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} key="agents_failed_count_divider">
|
||||
<Divider />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} key="actions_menu">
|
||||
<LiveQueryDetailsActionsMenu actionId={actionId} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[actionId, actionResultsData?.aggregations.failed, data?.actionDetails?.fields?.agents?.length]
|
||||
[actionResultsData?.aggregations.failed, data?.actionDetails?.fields?.agents?.length]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -137,7 +131,12 @@ const LiveQueryDetailsPageComponent = () => {
|
|||
{data?.actionDetails._source?.data?.query}
|
||||
</EuiCodeBlock>
|
||||
<EuiSpacer />
|
||||
<ResultTabs actionId={actionId} agentIds={data?.actionDetails?.fields?.agents} />
|
||||
<ResultTabs
|
||||
actionId={actionId}
|
||||
agentIds={data?.actionDetails?.fields?.agents}
|
||||
startDate={get(data, ['actionDetails', 'fields', '@timestamp', '0'])}
|
||||
endDate={get(data, 'actionDetails.fields.expiration[0]')}
|
||||
/>
|
||||
</WithHeaderLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useDiscoverLink } from '../../../common/hooks';
|
||||
|
||||
interface LiveQueryDetailsActionsMenuProps {
|
||||
actionId: string;
|
||||
}
|
||||
|
||||
const LiveQueryDetailsActionsMenuComponent: React.FC<LiveQueryDetailsActionsMenuProps> = ({
|
||||
actionId,
|
||||
}) => {
|
||||
const discoverLinkProps = useDiscoverLink({ filters: [{ key: 'action_id', value: actionId }] });
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
setPopover((currentIsPopoverOpen) => !currentIsPopoverOpen);
|
||||
}, []);
|
||||
|
||||
const closePopover = useCallback(() => {
|
||||
setPopover(false);
|
||||
}, []);
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
<EuiContextMenuItem key="copy" icon="copy" {...discoverLinkProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.liveQueryResults.viewResultsInDiscoverLabel"
|
||||
defaultMessage="View results in Discover"
|
||||
/>
|
||||
</EuiContextMenuItem>,
|
||||
],
|
||||
[discoverLinkProps]
|
||||
);
|
||||
|
||||
const button = useMemo(
|
||||
() => (
|
||||
<EuiButton iconType="arrowDown" iconSide="right" onClick={onButtonClick}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.liveQueryResults.actionsMenuButtonLabel"
|
||||
defaultMessage="Actions"
|
||||
/>
|
||||
</EuiButton>
|
||||
),
|
||||
[onButtonClick]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="liveQueryDetailsActionsMenu"
|
||||
button={button}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenuPanel size="s" items={items} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export const LiveQueryDetailsActionsMenu = React.memo(LiveQueryDetailsActionsMenuComponent);
|
|
@ -13,6 +13,8 @@ import {
|
|||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
@ -75,9 +77,17 @@ const ScheduledQueryGroupDetailsPageComponent = () => {
|
|||
<BetaBadge />
|
||||
</BetaBadgeRowWrapper>
|
||||
</EuiFlexItem>
|
||||
{data?.description && (
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="s">
|
||||
{data.description}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[data?.name, scheduledQueryGroupsListProps]
|
||||
[data?.description, data?.name, scheduledQueryGroupsListProps]
|
||||
);
|
||||
|
||||
const RightColumn = useMemo(
|
||||
|
|
|
@ -73,6 +73,10 @@ const ActiveStateSwitchComponent: React.FC<ActiveStateSwitchProps> = ({ item })
|
|||
)
|
||||
);
|
||||
},
|
||||
onError: (error) => {
|
||||
// @ts-expect-error update types
|
||||
toasts.addError(error, { title: error.body.error, toastMessage: error.body.message });
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ const AddQueryFlyoutComponent: React.FC<AddQueryFlyoutProps> = ({ onSave, onClos
|
|||
|
||||
return (
|
||||
<EuiPortal>
|
||||
<EuiFlyout size="s" ownFocus onClose={onClose} aria-labelledby="flyoutTitle">
|
||||
<EuiFlyout size="m" ownFocus onClose={onClose} aria-labelledby="flyoutTitle">
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="s">
|
||||
<h2 id="flyoutTitle">
|
||||
|
|
|
@ -91,7 +91,7 @@ export const EditQueryFlyout: React.FC<EditQueryFlyoutProps> = ({
|
|||
|
||||
return (
|
||||
<EuiPortal>
|
||||
<EuiFlyout size="s" ownFocus onClose={onClose} aria-labelledby="flyoutTitle">
|
||||
<EuiFlyout size="m" ownFocus onClose={onClose} aria-labelledby="flyoutTitle">
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="s">
|
||||
<h2 id="flyoutTitle">
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { findIndex, forEach, pullAt } from 'lodash';
|
||||
import { findIndex, forEach, pullAt, pullAllBy } from 'lodash';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import { produce } from 'immer';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { PackagePolicyInput, PackagePolicyInputStream } from '../../../../fleet/common';
|
||||
|
@ -50,6 +50,7 @@ const getNewStream = ({ id, interval, query, scheduledQueryGroupId }: GetNewStre
|
|||
const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({ field, scheduledQueryGroupId }) => {
|
||||
const [showAddQueryFlyout, setShowAddQueryFlyout] = useState(false);
|
||||
const [showEditQueryFlyout, setShowEditQueryFlyout] = useState<number>(-1);
|
||||
const [tableSelectedItems, setTableSelectedItems] = useState<PackagePolicyInputStream[]>([]);
|
||||
|
||||
const handleShowAddFlyout = useCallback(() => setShowAddQueryFlyout(true), []);
|
||||
const handleHideAddFlyout = useCallback(() => setShowAddQueryFlyout(false), []);
|
||||
|
@ -126,6 +127,17 @@ const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({ field, scheduledQu
|
|||
[handleHideAddFlyout, scheduledQueryGroupId, setValue]
|
||||
);
|
||||
|
||||
const handleDeleteQueries = useCallback(() => {
|
||||
setValue(
|
||||
produce((draft) => {
|
||||
pullAllBy(draft[0].streams, tableSelectedItems, 'vars.id.value');
|
||||
|
||||
return draft;
|
||||
})
|
||||
);
|
||||
setTableSelectedItems([]);
|
||||
}, [setValue, tableSelectedItems]);
|
||||
|
||||
const handlePackUpload = useCallback(
|
||||
(newQueries) => {
|
||||
setValue(
|
||||
|
@ -148,26 +160,42 @@ const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({ field, scheduledQu
|
|||
[scheduledQueryGroupId, setValue]
|
||||
);
|
||||
|
||||
const tableData = useMemo(() => ({ inputs: field.value }), [field.value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={handleShowAddFlyout} iconType="plusInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queriesForm.addQueryButtonLabel"
|
||||
defaultMessage="Add query"
|
||||
/>
|
||||
</EuiButton>
|
||||
{!tableSelectedItems.length ? (
|
||||
<EuiButton fill onClick={handleShowAddFlyout} iconType="plusInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queriesForm.addQueryButtonLabel"
|
||||
defaultMessage="Add query"
|
||||
/>
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton color="danger" onClick={handleDeleteQueries} iconType="trash">
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.table.deleteQueriesButtonLabel"
|
||||
defaultMessage="Delete {queriesCount, plural, one {# query} other {# queries}}"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{
|
||||
queriesCount: tableSelectedItems.length,
|
||||
}}
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
{field.value && field.value[0].streams?.length ? (
|
||||
<ScheduledQueryGroupQueriesTable
|
||||
editMode={true}
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
data={{ inputs: field.value }}
|
||||
data={tableData}
|
||||
onEditClick={handleEditClick}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
selectedItems={tableSelectedItems}
|
||||
setSelectedItems={setTableSelectedItems}
|
||||
/>
|
||||
) : null}
|
||||
<EuiSpacer />
|
||||
|
|
|
@ -5,19 +5,221 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import { EuiInMemoryTable, EuiCodeBlock, EuiButtonIcon } from '@elastic/eui';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiButtonEmpty,
|
||||
EuiCodeBlock,
|
||||
EuiButtonIcon,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
TypedLensByValueInput,
|
||||
PersistedIndexPatternLayer,
|
||||
PieVisualizationState,
|
||||
} from '../../../lens/public';
|
||||
import { PackagePolicy, PackagePolicyInputStream } from '../../../fleet/common';
|
||||
import { FilterStateStore } from '../../../../../src/plugins/data/common';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana';
|
||||
|
||||
interface ViewResultsInDiscoverActionProps {
|
||||
item: PackagePolicyInputStream;
|
||||
export enum ViewResultsActionButtonType {
|
||||
icon = 'icon',
|
||||
button = 'button',
|
||||
}
|
||||
|
||||
const ViewResultsInDiscoverAction: React.FC<ViewResultsInDiscoverActionProps> = ({ item }) => {
|
||||
interface ViewResultsInDiscoverActionProps {
|
||||
actionId: string;
|
||||
buttonType: ViewResultsActionButtonType;
|
||||
endDate?: string;
|
||||
startDate?: string;
|
||||
}
|
||||
|
||||
function getLensAttributes(actionId: string): TypedLensByValueInput['attributes'] {
|
||||
const dataLayer: PersistedIndexPatternLayer = {
|
||||
columnOrder: ['8690befd-fd69-4246-af4a-dd485d2a3b38', 'ed999e9d-204c-465b-897f-fe1a125b39ed'],
|
||||
columns: {
|
||||
'8690befd-fd69-4246-af4a-dd485d2a3b38': {
|
||||
sourceField: 'type',
|
||||
isBucketed: true,
|
||||
dataType: 'string',
|
||||
scale: 'ordinal',
|
||||
operationType: 'terms',
|
||||
label: 'Top values of type',
|
||||
params: {
|
||||
otherBucket: true,
|
||||
size: 5,
|
||||
missingBucket: false,
|
||||
orderBy: {
|
||||
columnId: 'ed999e9d-204c-465b-897f-fe1a125b39ed',
|
||||
type: 'column',
|
||||
},
|
||||
orderDirection: 'desc',
|
||||
},
|
||||
},
|
||||
'ed999e9d-204c-465b-897f-fe1a125b39ed': {
|
||||
sourceField: 'Records',
|
||||
isBucketed: false,
|
||||
dataType: 'number',
|
||||
scale: 'ratio',
|
||||
operationType: 'count',
|
||||
label: 'Count of records',
|
||||
},
|
||||
},
|
||||
incompleteColumns: {},
|
||||
};
|
||||
|
||||
const xyConfig: PieVisualizationState = {
|
||||
shape: 'pie',
|
||||
layers: [
|
||||
{
|
||||
legendDisplay: 'default',
|
||||
nestedLegend: false,
|
||||
layerId: 'layer1',
|
||||
metric: 'ed999e9d-204c-465b-897f-fe1a125b39ed',
|
||||
numberDisplay: 'percent',
|
||||
groups: ['8690befd-fd69-4246-af4a-dd485d2a3b38'],
|
||||
categoryDisplay: 'default',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return {
|
||||
visualizationType: 'lnsPie',
|
||||
title: `Action ${actionId} results`,
|
||||
references: [
|
||||
{
|
||||
id: 'logs-*',
|
||||
name: 'indexpattern-datasource-current-indexpattern',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
{
|
||||
id: 'logs-*',
|
||||
name: 'indexpattern-datasource-layer-layer1',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
{
|
||||
name: 'filter-index-pattern-0',
|
||||
id: 'logs-*',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
state: {
|
||||
datasourceStates: {
|
||||
indexpattern: {
|
||||
layers: {
|
||||
layer1: dataLayer,
|
||||
},
|
||||
},
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
$state: { store: FilterStateStore.APP_STATE },
|
||||
meta: {
|
||||
indexRefName: 'filter-index-pattern-0',
|
||||
negate: false,
|
||||
alias: null,
|
||||
disabled: false,
|
||||
params: {
|
||||
query: actionId,
|
||||
},
|
||||
type: 'phrase',
|
||||
key: 'action_id',
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
action_id: actionId,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
query: { language: 'kuery', query: '' },
|
||||
visualization: xyConfig,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const ViewResultsInLensActionComponent: React.FC<ViewResultsInDiscoverActionProps> = ({
|
||||
actionId,
|
||||
buttonType,
|
||||
endDate,
|
||||
startDate,
|
||||
}) => {
|
||||
const lensService = useKibana().services.lens;
|
||||
|
||||
const handleClick = useCallback(
|
||||
(event) => {
|
||||
const openInNewWindow = !(!isModifiedEvent(event) && isLeftClickEvent(event));
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
lensService?.navigateToPrefilledEditor(
|
||||
{
|
||||
id: '',
|
||||
timeRange: {
|
||||
from: startDate ?? 'now-1d',
|
||||
to: endDate ?? 'now',
|
||||
mode: startDate || endDate ? 'absolute' : 'relative',
|
||||
},
|
||||
attributes: getLensAttributes(actionId),
|
||||
},
|
||||
openInNewWindow
|
||||
);
|
||||
},
|
||||
[actionId, endDate, lensService, startDate]
|
||||
);
|
||||
|
||||
if (buttonType === ViewResultsActionButtonType.button) {
|
||||
return (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="lensApp"
|
||||
onClick={handleClick}
|
||||
disabled={!lensService?.canUseEditor()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel"
|
||||
defaultMessage="View results in Lens"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip
|
||||
content={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View results in Lens',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="lensApp"
|
||||
disabled={!lensService?.canUseEditor()}
|
||||
onClick={handleClick}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View results in Lens',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewResultsInLensAction = React.memo(ViewResultsInLensActionComponent);
|
||||
|
||||
const ViewResultsInDiscoverActionComponent: React.FC<ViewResultsInDiscoverActionProps> = ({
|
||||
actionId,
|
||||
buttonType,
|
||||
endDate,
|
||||
startDate,
|
||||
}) => {
|
||||
const urlGenerator = useKibana().services.discover?.urlGenerator;
|
||||
const [discoverUrl, setDiscoverUrl] = useState<string>('');
|
||||
|
||||
|
@ -36,40 +238,77 @@ const ViewResultsInDiscoverAction: React.FC<ViewResultsInDiscoverActionProps> =
|
|||
disabled: false,
|
||||
type: 'phrase',
|
||||
key: 'action_id',
|
||||
params: { query: item.vars?.id.value },
|
||||
params: { query: actionId },
|
||||
},
|
||||
query: { match_phrase: { action_id: item.vars?.id.value } },
|
||||
query: { match_phrase: { action_id: actionId } },
|
||||
$state: { store: FilterStateStore.APP_STATE },
|
||||
},
|
||||
],
|
||||
refreshInterval: {
|
||||
pause: true,
|
||||
value: 0,
|
||||
},
|
||||
timeRange:
|
||||
startDate && endDate
|
||||
? {
|
||||
to: endDate,
|
||||
from: startDate,
|
||||
mode: 'absolute',
|
||||
}
|
||||
: {
|
||||
to: 'now',
|
||||
from: 'now-15m',
|
||||
mode: 'relative',
|
||||
},
|
||||
});
|
||||
setDiscoverUrl(newUrl);
|
||||
};
|
||||
getDiscoverUrl();
|
||||
}, [item.vars?.id.value, urlGenerator]);
|
||||
}, [actionId, endDate, startDate, urlGenerator]);
|
||||
|
||||
if (buttonType === ViewResultsActionButtonType.button) {
|
||||
return (
|
||||
<EuiButtonEmpty size="xs" iconType="discoverApp" href={discoverUrl}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel"
|
||||
defaultMessage="View results in Discover"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
iconType="visTable"
|
||||
href={discoverUrl}
|
||||
aria-label={i18n.translate(
|
||||
<EuiToolTip
|
||||
content={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Check results of {queryName} in Discover',
|
||||
values: {
|
||||
queryName: item.vars?.id.value,
|
||||
},
|
||||
defaultMessage: 'View results in Discover',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="discoverApp"
|
||||
href={discoverUrl}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View results in Discover',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ViewResultsInDiscoverAction = React.memo(ViewResultsInDiscoverActionComponent);
|
||||
|
||||
interface ScheduledQueryGroupQueriesTableProps {
|
||||
data: Pick<PackagePolicy, 'inputs'>;
|
||||
editMode?: boolean;
|
||||
onDeleteClick?: (item: PackagePolicyInputStream) => void;
|
||||
onEditClick?: (item: PackagePolicyInputStream) => void;
|
||||
selectedItems?: PackagePolicyInputStream[];
|
||||
setSelectedItems?: (selection: PackagePolicyInputStream[]) => void;
|
||||
}
|
||||
|
||||
const ScheduledQueryGroupQueriesTableComponent: React.FC<ScheduledQueryGroupQueriesTableProps> = ({
|
||||
|
@ -77,6 +316,8 @@ const ScheduledQueryGroupQueriesTableComponent: React.FC<ScheduledQueryGroupQuer
|
|||
editMode = false,
|
||||
onDeleteClick,
|
||||
onEditClick,
|
||||
selectedItems,
|
||||
setSelectedItems,
|
||||
}) => {
|
||||
const renderDeleteAction = useCallback(
|
||||
(item: PackagePolicyInputStream) => (
|
||||
|
@ -132,7 +373,22 @@ const ScheduledQueryGroupQueriesTableComponent: React.FC<ScheduledQueryGroupQuer
|
|||
);
|
||||
|
||||
const renderDiscoverResultsAction = useCallback(
|
||||
(item) => <ViewResultsInDiscoverAction item={item} />,
|
||||
(item) => (
|
||||
<ViewResultsInDiscoverAction
|
||||
actionId={item.vars?.id.value}
|
||||
buttonType={ViewResultsActionButtonType.icon}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const renderLensResultsAction = useCallback(
|
||||
(item) => (
|
||||
<ViewResultsInLensAction
|
||||
actionId={item.vars?.id.value}
|
||||
buttonType={ViewResultsActionButtonType.icon}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
|
@ -184,29 +440,50 @@ const ScheduledQueryGroupQueriesTableComponent: React.FC<ScheduledQueryGroupQuer
|
|||
{
|
||||
render: renderDiscoverResultsAction,
|
||||
},
|
||||
{
|
||||
render: renderLensResultsAction,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[editMode, renderDeleteAction, renderDiscoverResultsAction, renderEditAction, renderQueryColumn]
|
||||
[
|
||||
editMode,
|
||||
renderDeleteAction,
|
||||
renderDiscoverResultsAction,
|
||||
renderEditAction,
|
||||
renderLensResultsAction,
|
||||
renderQueryColumn,
|
||||
]
|
||||
);
|
||||
|
||||
const sorting = useMemo(
|
||||
() => ({
|
||||
sort: {
|
||||
field: 'vars.id.value',
|
||||
field: 'vars.id.value' as keyof PackagePolicyInputStream,
|
||||
direction: 'asc' as const,
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const itemId = useCallback((item: PackagePolicyInputStream) => get('vars.id.value', item), []);
|
||||
|
||||
const selection = useMemo(
|
||||
() => ({
|
||||
onSelectionChange: setSelectedItems,
|
||||
initialSelected: selectedItems,
|
||||
}),
|
||||
[selectedItems, setSelectedItems]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiInMemoryTable<PackagePolicyInputStream>
|
||||
<EuiBasicTable<PackagePolicyInputStream>
|
||||
items={data.inputs[0].streams}
|
||||
itemId="vars.id.value"
|
||||
isExpandable={true}
|
||||
itemId={itemId}
|
||||
columns={columns}
|
||||
sorting={sorting}
|
||||
selection={editMode ? selection : undefined}
|
||||
isSelectable={editMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { DiscoverStart } from '../../../../src/plugins/discover/public';
|
||||
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { FleetStart } from '../../fleet/public';
|
||||
import { LensPublicStart } from '../../../plugins/lens/public';
|
||||
import { CoreStart } from '../../../../src/core/public';
|
||||
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
|
||||
import {
|
||||
|
@ -28,6 +29,7 @@ export interface StartPlugins {
|
|||
discover: DiscoverStart;
|
||||
data: DataPublicPluginStart;
|
||||
fleet: FleetStart;
|
||||
lens?: LensPublicStart;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ export const parseAgentSelection = async (
|
|||
const { allAgentsSelected, platformsSelected, policiesSelected, agents } = agentSelection;
|
||||
const agentService = context.service.getAgentService();
|
||||
const packagePolicyService = context.service.getPackagePolicyService();
|
||||
const kueryFragments = ['active:true'];
|
||||
const kueryFragments = [];
|
||||
|
||||
if (agentService && packagePolicyService) {
|
||||
const osqueryPolicies = await aggregateResults(async (page, perPage) => {
|
||||
|
@ -55,7 +55,7 @@ export const parseAgentSelection = async (
|
|||
});
|
||||
return { results: items.map((it) => it.policy_id), total };
|
||||
});
|
||||
kueryFragments.push(`policy_id:(${uniq(osqueryPolicies).join(',')})`);
|
||||
kueryFragments.push(`policy_id:(${uniq(osqueryPolicies).join(' or ')})`);
|
||||
if (allAgentsSelected) {
|
||||
const kuery = kueryFragments.join(' and ');
|
||||
const fetchedAgents = await aggregateResults(async (page, perPage) => {
|
||||
|
@ -72,10 +72,10 @@ export const parseAgentSelection = async (
|
|||
if (platformsSelected.length > 0 || policiesSelected.length > 0) {
|
||||
const groupFragments = [];
|
||||
if (platformsSelected.length) {
|
||||
groupFragments.push(`local_metadata.os.platform:(${platformsSelected.join(',')})`);
|
||||
groupFragments.push(`local_metadata.os.platform:(${platformsSelected.join(' or ')})`);
|
||||
}
|
||||
if (policiesSelected.length) {
|
||||
groupFragments.push(`policy_id:(${policiesSelected.join(',')})`);
|
||||
groupFragments.push(`policy_id:(${policiesSelected.join(' or ')})`);
|
||||
}
|
||||
kueryFragments.push(`(${groupFragments.join(' or ')})`);
|
||||
const kuery = kueryFragments.join(' and ');
|
||||
|
|
|
@ -41,7 +41,7 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
|||
);
|
||||
|
||||
if (!selectedAgents.length) {
|
||||
throw new Error('No agents found for selection, aborting.');
|
||||
return response.badRequest({ body: new Error('No agents found for selection') });
|
||||
}
|
||||
|
||||
const action = {
|
||||
|
|
|
@ -53,13 +53,12 @@ export const buildResultsQuery = ({
|
|||
size: querySize,
|
||||
track_total_hits: true,
|
||||
fields: agentId ? ['osquery.*'] : ['agent.*', 'osquery.*'],
|
||||
sort: [
|
||||
{
|
||||
[sort.field]: {
|
||||
order: sort.direction,
|
||||
sort:
|
||||
sort?.map((sortConfig) => ({
|
||||
[sortConfig.field]: {
|
||||
order: sortConfig.direction,
|
||||
},
|
||||
},
|
||||
],
|
||||
})) ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue