Co-authored-by: Patryk Kopyciński <patryk.kopycinski@elastic.co>
This commit is contained in:
parent
de660291d4
commit
effc87beff
|
@ -349,7 +349,7 @@
|
|||
"react-moment-proptypes": "^1.7.0",
|
||||
"react-monaco-editor": "^0.41.2",
|
||||
"react-popper-tooltip": "^2.10.1",
|
||||
"react-query": "^3.21.0",
|
||||
"react-query": "^3.21.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-resizable": "^1.7.5",
|
||||
"react-resize-detector": "^4.2.0",
|
||||
|
|
|
@ -6,28 +6,16 @@
|
|||
*/
|
||||
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
export const useActionResultsPrivileges = () => {
|
||||
const { http } = useKibana().services;
|
||||
const setErrorToast = useErrorToast();
|
||||
|
||||
return useQuery(
|
||||
['actionResultsPrivileges'],
|
||||
() => http.get('/internal/osquery/privileges_check'),
|
||||
{
|
||||
keepPreviousData: true,
|
||||
select: (response) => response?.has_all_requested ?? false,
|
||||
onSuccess: () => setErrorToast(),
|
||||
onError: (error: Error) =>
|
||||
setErrorToast(error, {
|
||||
title: i18n.translate('xpack.osquery.action_results_privileges.fetchError', {
|
||||
defaultMessage: 'Error while fetching action results privileges',
|
||||
}),
|
||||
}),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,8 +27,6 @@ export interface DynamicPagePathValues {
|
|||
[key: string]: string;
|
||||
}
|
||||
|
||||
export const BASE_PATH = '/app/fleet';
|
||||
|
||||
// If routing paths are changed here, please also check to see if
|
||||
// `pagePathGetters()`, below, needs any modifications
|
||||
export const PAGE_ROUTING_PATHS = {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab } from '@elastic/eui';
|
||||
|
@ -14,10 +16,17 @@ import { Container, Nav, Wrapper } from './layouts';
|
|||
import { OsqueryAppRoutes } from '../routes';
|
||||
import { useRouterNavigate } from '../common/lib/kibana';
|
||||
import { ManageIntegrationLink } from './manage_integration_link';
|
||||
import { useOsqueryIntegrationStatus } from '../common/hooks';
|
||||
import { OsqueryAppEmptyState } from './empty_state';
|
||||
|
||||
const OsqueryAppComponent = () => {
|
||||
const location = useLocation();
|
||||
const section = useMemo(() => location.pathname.split('/')[1] ?? 'overview', [location.pathname]);
|
||||
const { data: osqueryIntegration, isFetched } = useOsqueryIntegrationStatus();
|
||||
|
||||
if (isFetched && osqueryIntegration.install_status !== 'installed') {
|
||||
return <OsqueryAppEmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
|
|
86
x-pack/plugins/osquery/public/components/empty_state.tsx
Normal file
86
x-pack/plugins/osquery/public/components/empty_state.tsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 React, { useCallback, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
import { KibanaPageTemplate } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { INTEGRATIONS_PLUGIN_ID } from '../../../fleet/common';
|
||||
import { pagePathGetters } from '../../../fleet/public';
|
||||
import { isModifiedEvent, isLeftClickEvent, useKibana } from '../common/lib/kibana';
|
||||
import { OsqueryIcon } from './osquery_icon';
|
||||
import { useBreadcrumbs } from '../common/hooks/use_breadcrumbs';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
||||
|
||||
const OsqueryAppEmptyStateComponent = () => {
|
||||
useBreadcrumbs('base');
|
||||
|
||||
const {
|
||||
application: { getUrlForApp, navigateToApp },
|
||||
} = useKibana().services;
|
||||
|
||||
const integrationHref = useMemo(() => {
|
||||
return getUrlForApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path: pagePathGetters.integration_details_overview({
|
||||
pkgkey: OSQUERY_INTEGRATION_NAME,
|
||||
})[1],
|
||||
});
|
||||
}, [getUrlForApp]);
|
||||
|
||||
const integrationClick = useCallback(
|
||||
(event) => {
|
||||
if (!isModifiedEvent(event) && isLeftClickEvent(event)) {
|
||||
event.preventDefault();
|
||||
return navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path: pagePathGetters.integration_details_overview({
|
||||
pkgkey: OSQUERY_INTEGRATION_NAME,
|
||||
})[1],
|
||||
});
|
||||
}
|
||||
},
|
||||
[navigateToApp]
|
||||
);
|
||||
|
||||
const pageHeader = useMemo(
|
||||
() => ({
|
||||
iconType: OsqueryIcon,
|
||||
pageTitle: (
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.emptyState.pageTitle"
|
||||
defaultMessage="Add Osquery Manager"
|
||||
/>
|
||||
),
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.emptyState.pageDescription"
|
||||
defaultMessage="Add this integration to run and schedule queries for Elastic Agent."
|
||||
/>
|
||||
),
|
||||
rightSideItems: [
|
||||
// eslint-disable-next-line @elastic/eui/href-or-on-click
|
||||
<EuiButton
|
||||
key="button"
|
||||
fill
|
||||
href={integrationHref}
|
||||
onClick={integrationClick}
|
||||
iconType="plusInCircleFilled"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.emptyState.addOsqueryManagerButton"
|
||||
defaultMessage="Add Osquery Manager"
|
||||
/>
|
||||
</EuiButton>,
|
||||
],
|
||||
}),
|
||||
[integrationClick, integrationHref]
|
||||
);
|
||||
|
||||
return <KibanaPageTemplate isEmptyState={true} pageHeader={pageHeader} />;
|
||||
};
|
||||
|
||||
export const OsqueryAppEmptyState = React.memo(OsqueryAppEmptyStateComponent);
|
|
@ -24,11 +24,9 @@ const ManageIntegrationLinkComponent = () => {
|
|||
const integrationHref = useMemo(() => {
|
||||
if (osqueryIntegration) {
|
||||
return getUrlForApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path:
|
||||
'#' +
|
||||
pagePathGetters.integration_details_policies({
|
||||
pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`,
|
||||
})[1],
|
||||
path: pagePathGetters.integration_details_policies({
|
||||
pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`,
|
||||
})[1],
|
||||
});
|
||||
}
|
||||
}, [getUrlForApp, osqueryIntegration]);
|
||||
|
@ -39,11 +37,9 @@ const ManageIntegrationLinkComponent = () => {
|
|||
event.preventDefault();
|
||||
if (osqueryIntegration) {
|
||||
return navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path:
|
||||
'#' +
|
||||
pagePathGetters.integration_details_policies({
|
||||
pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`,
|
||||
})[1],
|
||||
path: pagePathGetters.integration_details_policies({
|
||||
pkgkey: `${osqueryIntegration.name}-${osqueryIntegration.version}`,
|
||||
})[1],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
),
|
||||
});
|
||||
|
||||
const { setFieldValue, submit } = form;
|
||||
const { setFieldValue, submit, isSubmitting } = form;
|
||||
|
||||
const actionId = useMemo(() => data?.actions[0].action_id, [data?.actions]);
|
||||
const agentIds = useMemo(() => data?.actions[0].agents, [data?.actions]);
|
||||
|
@ -185,7 +185,10 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton disabled={!agentSelected || !queryValueProvided} onClick={submit}>
|
||||
<EuiButton
|
||||
disabled={!agentSelected || !queryValueProvided || isSubmitting}
|
||||
onClick={submit}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.liveQueryForm.form.submitButtonLabel"
|
||||
defaultMessage="Submit"
|
||||
|
@ -196,13 +199,14 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
</>
|
||||
),
|
||||
[
|
||||
agentSelected,
|
||||
permissions.writeSavedQueries,
|
||||
handleShowSaveQueryFlout,
|
||||
queryComponentProps,
|
||||
singleAgentMode,
|
||||
permissions.writeSavedQueries,
|
||||
agentSelected,
|
||||
queryValueProvided,
|
||||
resultsStatus,
|
||||
singleAgentMode,
|
||||
handleShowSaveQueryFlout,
|
||||
isSubmitting,
|
||||
submit,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -51,7 +51,7 @@ const AddPackQueryFormComponent = ({ handleSubmit }) => {
|
|||
},
|
||||
},
|
||||
});
|
||||
const { submit } = form;
|
||||
const { submit, isSubmitting } = form;
|
||||
|
||||
const createSavedQueryMutation = useMutation(
|
||||
(payload) => http.post(`/internal/osquery/saved_query`, { body: JSON.stringify(payload) }),
|
||||
|
@ -108,7 +108,7 @@ const AddPackQueryFormComponent = ({ handleSubmit }) => {
|
|||
<EuiSpacer />
|
||||
<CommonUseField path="interval" />
|
||||
<EuiSpacer />
|
||||
<EuiButton fill onClick={submit}>
|
||||
<EuiButton isLoading={isSubmitting} fill onClick={submit}>
|
||||
{'Add query'}
|
||||
</EuiButton>
|
||||
</Form>
|
||||
|
|
|
@ -40,7 +40,7 @@ const PackFormComponent = ({ data, handleSubmit }) => {
|
|||
},
|
||||
},
|
||||
});
|
||||
const { submit } = form;
|
||||
const { submit, isSubmitting } = form;
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
|
@ -50,7 +50,7 @@ const PackFormComponent = ({ data, handleSubmit }) => {
|
|||
<EuiSpacer />
|
||||
<CommonUseField path="queries" component={PackQueriesField} />
|
||||
<EuiSpacer />
|
||||
<EuiButton fill onClick={submit}>
|
||||
<EuiButton isLoading={isSubmitting} fill onClick={submit}>
|
||||
{'Save pack'}
|
||||
</EuiButton>
|
||||
</Form>
|
||||
|
|
|
@ -38,6 +38,7 @@ const EditSavedQueryFormComponent: React.FC<EditSavedQueryFormProps> = ({
|
|||
defaultValue,
|
||||
handleSubmit,
|
||||
});
|
||||
const { submit, isSubmitting } = form;
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
|
@ -58,12 +59,12 @@ const EditSavedQueryFormComponent: React.FC<EditSavedQueryFormProps> = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
// isLoading={isLoading}
|
||||
isLoading={isSubmitting}
|
||||
color="primary"
|
||||
fill
|
||||
size="m"
|
||||
iconType="save"
|
||||
onClick={form.submit}
|
||||
onClick={submit}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.editSavedQuery.form.updateQueryButtonLabel"
|
||||
|
|
|
@ -36,6 +36,7 @@ const NewSavedQueryFormComponent: React.FC<NewSavedQueryFormProps> = ({
|
|||
defaultValue,
|
||||
handleSubmit,
|
||||
});
|
||||
const { submit, isSubmitting } = form;
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
|
@ -54,12 +55,12 @@ const NewSavedQueryFormComponent: React.FC<NewSavedQueryFormProps> = ({
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
// isLoading={isLoading}
|
||||
isLoading={isSubmitting}
|
||||
color="primary"
|
||||
fill
|
||||
size="m"
|
||||
iconType="save"
|
||||
onClick={form.submit}
|
||||
onClick={submit}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.addSavedQuery.form.saveQueryButtonLabel"
|
||||
|
|
|
@ -25,7 +25,10 @@ const EditScheduledQueryGroupPageComponent = () => {
|
|||
|
||||
const { data } = useScheduledQueryGroup({ scheduledQueryGroupId });
|
||||
|
||||
useBreadcrumbs('scheduled_query_group_edit', { scheduledQueryGroupName: data?.name ?? '' });
|
||||
useBreadcrumbs('scheduled_query_group_edit', {
|
||||
scheduledQueryGroupId: data?.id ?? '',
|
||||
scheduledQueryGroupName: data?.name ?? '',
|
||||
});
|
||||
|
||||
const LeftColumn = useMemo(
|
||||
() => (
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export const SAVED_QUERIES_ID = 'savedQueryList';
|
||||
export const SAVED_QUERY_ID = 'savedQuery';
|
||||
|
|
|
@ -42,6 +42,7 @@ const SavedQueryFlyoutComponent: React.FC<AddQueryFlyoutProps> = ({ defaultValue
|
|||
defaultValue,
|
||||
handleSubmit,
|
||||
});
|
||||
const { submit, isSubmitting } = form;
|
||||
|
||||
return (
|
||||
<EuiPortal>
|
||||
|
@ -72,7 +73,7 @@ const SavedQueryFlyoutComponent: React.FC<AddQueryFlyoutProps> = ({ defaultValue
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={form.submit} fill>
|
||||
<EuiButton isLoading={isSubmitting} onClick={submit} fill>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queryFlyoutForm.saveButtonLabel"
|
||||
defaultMessage="Save"
|
||||
|
|
|
@ -12,8 +12,7 @@ import { useKibana } from '../common/lib/kibana';
|
|||
import { savedQuerySavedObjectType } from '../../common/types';
|
||||
import { pagePathGetters } from '../common/page_paths';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
export const SAVED_QUERY_ID = 'savedQuery';
|
||||
import { SAVED_QUERY_ID } from './constants';
|
||||
|
||||
interface UseSavedQueryProps {
|
||||
savedQueryId: string;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useKibana } from '../common/lib/kibana';
|
|||
import { savedQuerySavedObjectType } from '../../common/types';
|
||||
import { PLUGIN_ID } from '../../common';
|
||||
import { pagePathGetters } from '../common/page_paths';
|
||||
import { SAVED_QUERIES_ID } from './constants';
|
||||
import { SAVED_QUERIES_ID, SAVED_QUERY_ID } from './constants';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
interface UseUpdateSavedQueryProps {
|
||||
|
@ -62,6 +62,7 @@ export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps)
|
|||
},
|
||||
onSuccess: (payload) => {
|
||||
queryClient.invalidateQueries(SAVED_QUERIES_ID);
|
||||
queryClient.invalidateQueries([SAVED_QUERY_ID, { savedQueryId }]);
|
||||
navigateToApp(PLUGIN_ID, { path: pagePathGetters.saved_queries() });
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.osquery.editSavedQuery.successToastMessageText', {
|
||||
|
|
|
@ -88,7 +88,7 @@ const ScheduledQueryGroupFormComponent: React.FC<ScheduledQueryGroupFormProps> =
|
|||
`scheduled_query_groups/${editMode ? defaultValue?.id : ''}`
|
||||
);
|
||||
|
||||
const { isLoading, mutateAsync } = useMutation(
|
||||
const { mutateAsync } = useMutation(
|
||||
(payload: Record<string, unknown>) =>
|
||||
editMode && defaultValue?.id
|
||||
? http.put(packagePolicyRouteService.getUpdatePath(defaultValue.id), {
|
||||
|
@ -248,7 +248,7 @@ const ScheduledQueryGroupFormComponent: React.FC<ScheduledQueryGroupFormProps> =
|
|||
),
|
||||
});
|
||||
|
||||
const { setFieldValue, submit } = form;
|
||||
const { setFieldValue, submit, isSubmitting } = form;
|
||||
|
||||
const policyIdEuiFieldProps = useMemo(
|
||||
() => ({ isDisabled: !!defaultValue, options: agentPolicyOptions }),
|
||||
|
@ -368,7 +368,7 @@ const ScheduledQueryGroupFormComponent: React.FC<ScheduledQueryGroupFormProps> =
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isLoading={isLoading}
|
||||
isLoading={isSubmitting}
|
||||
color="primary"
|
||||
fill
|
||||
size="m"
|
||||
|
|
|
@ -342,7 +342,8 @@ const getEcsFieldValidator = (editForm: boolean) => (
|
|||
)
|
||||
)(args);
|
||||
|
||||
if (fieldRequiredError && (!!(!editForm && args.formData.value?.field.length) || editForm)) {
|
||||
// @ts-expect-error update types
|
||||
if (fieldRequiredError && ((!editForm && args.formData['value.field'].length) || editForm)) {
|
||||
return fieldRequiredError;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiFlyout,
|
||||
|
@ -66,7 +67,7 @@ const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({
|
|||
if (isValid && ecsFieldValue) {
|
||||
onSave({
|
||||
...payload,
|
||||
ecs_mapping: ecsFieldValue,
|
||||
...(isEmpty(ecsFieldValue) ? {} : { ecs_mapping: ecsFieldValue }),
|
||||
});
|
||||
onClose();
|
||||
}
|
||||
|
@ -81,7 +82,7 @@ const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({
|
|||
[integrationPackageVersion]
|
||||
);
|
||||
|
||||
const { submit, setFieldValue, reset } = form;
|
||||
const { submit, setFieldValue, reset, isSubmitting } = form;
|
||||
|
||||
const [{ query }] = useFormData({
|
||||
form,
|
||||
|
@ -245,7 +246,7 @@ const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={submit} fill>
|
||||
<EuiButton isLoading={isSubmitting} onClick={submit} fill>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queryFlyoutForm.saveButtonLabel"
|
||||
defaultMessage="Save"
|
||||
|
|
|
@ -21,14 +21,14 @@ import {
|
|||
EuiPanel,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, FormattedDate, FormattedTime, FormattedRelative } from '@kbn/i18n/react';
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import {
|
||||
TypedLensByValueInput,
|
||||
PersistedIndexPatternLayer,
|
||||
PieVisualizationState,
|
||||
} from '../../../lens/public';
|
||||
import { FilterStateStore } from '../../../../../src/plugins/data/common';
|
||||
import { FilterStateStore, IndexPattern } from '../../../../../src/plugins/data/common';
|
||||
import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana';
|
||||
import { OsqueryManagerPackagePolicyInputStream } from '../../common/types';
|
||||
import { ScheduledQueryErrorsTable } from './scheduled_query_errors_table';
|
||||
|
@ -391,16 +391,21 @@ const ScheduledQueryLastResults: React.FC<ScheduledQueryLastResultsProps> = ({
|
|||
toggleErrors,
|
||||
expanded,
|
||||
}) => {
|
||||
const data = useKibana().services.data;
|
||||
const [logsIndexPattern, setLogsIndexPattern] = useState<IndexPattern | undefined>(undefined);
|
||||
|
||||
const { data: lastResultsData, isFetched } = useScheduledQueryGroupQueryLastResults({
|
||||
actionId,
|
||||
agentIds,
|
||||
interval,
|
||||
logsIndexPattern,
|
||||
});
|
||||
|
||||
const { data: errorsData, isFetched: errorsFetched } = useScheduledQueryGroupQueryErrors({
|
||||
actionId,
|
||||
agentIds,
|
||||
interval,
|
||||
logsIndexPattern,
|
||||
});
|
||||
|
||||
const handleErrorsToggle = useCallback(() => toggleErrors({ queryId, interval }), [
|
||||
|
@ -409,20 +414,41 @@ const ScheduledQueryLastResults: React.FC<ScheduledQueryLastResultsProps> = ({
|
|||
toggleErrors,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLogsIndexPattern = async () => {
|
||||
const indexPattern = await data.indexPatterns.find('logs-*');
|
||||
|
||||
setLogsIndexPattern(indexPattern[0]);
|
||||
};
|
||||
fetchLogsIndexPattern();
|
||||
}, [data.indexPatterns]);
|
||||
|
||||
if (!isFetched || !errorsFetched) {
|
||||
return <EuiLoadingSpinner />;
|
||||
}
|
||||
|
||||
if (!lastResultsData) {
|
||||
if (!lastResultsData && !errorsData?.total) {
|
||||
return <>{'-'}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={4}>
|
||||
{lastResultsData.first_event_ingested_time?.value ? (
|
||||
<EuiToolTip content={lastResultsData.first_event_ingested_time?.value}>
|
||||
<>{moment(lastResultsData.first_event_ingested_time?.value).fromNow()}</>
|
||||
{lastResultsData?.['@timestamp'] ? (
|
||||
<EuiToolTip
|
||||
content={
|
||||
<>
|
||||
<FormattedDate
|
||||
value={lastResultsData['@timestamp']}
|
||||
year="numeric"
|
||||
month="short"
|
||||
day="2-digit"
|
||||
/>{' '}
|
||||
<FormattedTime value={lastResultsData['@timestamp']} timeZoneName="short" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormattedRelative value={lastResultsData['@timestamp']} />
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
'-'
|
||||
|
@ -432,10 +458,17 @@ const ScheduledQueryLastResults: React.FC<ScheduledQueryLastResultsProps> = ({
|
|||
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued">
|
||||
{lastResultsData?.doc_count ?? 0}
|
||||
{lastResultsData?.docCount ?? 0}
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{'Documents'}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.queriesStatusTable.documentLabelText"
|
||||
defaultMessage="{count, plural, one {Document} other {Documents}}"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{ count: lastResultsData?.docCount as number }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
@ -443,10 +476,17 @@ const ScheduledQueryLastResults: React.FC<ScheduledQueryLastResultsProps> = ({
|
|||
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued">
|
||||
{lastResultsData?.unique_agents?.value ?? 0}
|
||||
{lastResultsData?.uniqueAgentsCount ?? 0}
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{'Agents'}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.queriesStatusTable.agentsLabelText"
|
||||
defaultMessage="{count, plural, one {Agent} other {Agents}}"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{ count: agentIds?.length }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
@ -458,7 +498,15 @@ const ScheduledQueryLastResults: React.FC<ScheduledQueryLastResultsProps> = ({
|
|||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>{'Errors'}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.queriesStatusTable.errorsLabelText"
|
||||
defaultMessage="{count, plural, one {Error} other {Errors}}"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{ count: errorsData?.total as number }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { useQuery } from 'react-query';
|
||||
import { SortDirection } from '../../../../../src/plugins/data/common';
|
||||
import { IndexPattern, SortDirection } from '../../../../../src/plugins/data/common';
|
||||
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
|
@ -14,6 +14,7 @@ interface UseScheduledQueryGroupQueryErrorsProps {
|
|||
actionId: string;
|
||||
agentIds?: string[];
|
||||
interval: number;
|
||||
logsIndexPattern?: IndexPattern;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
|
@ -21,6 +22,7 @@ export const useScheduledQueryGroupQueryErrors = ({
|
|||
actionId,
|
||||
agentIds,
|
||||
interval,
|
||||
logsIndexPattern,
|
||||
skip = false,
|
||||
}: UseScheduledQueryGroupQueryErrorsProps) => {
|
||||
const data = useKibana().services.data;
|
||||
|
@ -28,9 +30,8 @@ export const useScheduledQueryGroupQueryErrors = ({
|
|||
return useQuery(
|
||||
['scheduledQueryErrors', { actionId, interval }],
|
||||
async () => {
|
||||
const indexPattern = await data.indexPatterns.find('logs-*');
|
||||
const searchSource = await data.search.searchSource.create({
|
||||
index: indexPattern[0],
|
||||
index: logsIndexPattern,
|
||||
fields: ['*'],
|
||||
sort: [
|
||||
{
|
||||
|
@ -80,7 +81,7 @@ export const useScheduledQueryGroupQueryErrors = ({
|
|||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
enabled: !!(!skip && actionId && interval && agentIds?.length),
|
||||
enabled: !!(!skip && actionId && interval && agentIds?.length && logsIndexPattern),
|
||||
select: (response) => response.rawResponse.hits ?? [],
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
*/
|
||||
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { IndexPattern } from '../../../../../src/plugins/data/common';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
interface UseScheduledQueryGroupQueryLastResultsProps {
|
||||
actionId: string;
|
||||
agentIds?: string[];
|
||||
interval: number;
|
||||
logsIndexPattern?: IndexPattern;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
|
@ -20,6 +21,7 @@ export const useScheduledQueryGroupQueryLastResults = ({
|
|||
actionId,
|
||||
agentIds,
|
||||
interval,
|
||||
logsIndexPattern,
|
||||
skip = false,
|
||||
}: UseScheduledQueryGroupQueryLastResultsProps) => {
|
||||
const data = useKibana().services.data;
|
||||
|
@ -27,23 +29,9 @@ export const useScheduledQueryGroupQueryLastResults = ({
|
|||
return useQuery(
|
||||
['scheduledQueryLastResults', { actionId }],
|
||||
async () => {
|
||||
const indexPattern = await data.indexPatterns.find('logs-*');
|
||||
const searchSource = await data.search.searchSource.create({
|
||||
index: indexPattern[0],
|
||||
size: 0,
|
||||
aggs: {
|
||||
runs: {
|
||||
terms: {
|
||||
field: 'response_id',
|
||||
order: { first_event_ingested_time: 'desc' },
|
||||
size: 1,
|
||||
},
|
||||
aggs: {
|
||||
first_event_ingested_time: { min: { field: '@timestamp' } },
|
||||
unique_agents: { cardinality: { field: 'agent.id' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
const lastResultsSearchSource = await data.search.searchSource.create({
|
||||
index: logsIndexPattern,
|
||||
size: 1,
|
||||
query: {
|
||||
// @ts-expect-error update types
|
||||
bool: {
|
||||
|
@ -59,26 +47,62 @@ export const useScheduledQueryGroupQueryLastResults = ({
|
|||
action_id: actionId,
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: `now-${interval * 2}s`,
|
||||
lte: 'now',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return searchSource.fetch$().toPromise();
|
||||
const lastResultsResponse = await lastResultsSearchSource.fetch$().toPromise();
|
||||
|
||||
const responseId = lastResultsResponse.rawResponse?.hits?.hits[0]?._source?.response_id;
|
||||
|
||||
if (responseId) {
|
||||
const aggsSearchSource = await data.search.searchSource.create({
|
||||
index: logsIndexPattern,
|
||||
size: 0,
|
||||
aggs: {
|
||||
unique_agents: { cardinality: { field: 'agent.id' } },
|
||||
},
|
||||
query: {
|
||||
// @ts-expect-error update types
|
||||
bool: {
|
||||
should: agentIds?.map((agentId) => ({
|
||||
match_phrase: {
|
||||
'agent.id': agentId,
|
||||
},
|
||||
})),
|
||||
minimum_should_match: 1,
|
||||
filter: [
|
||||
{
|
||||
match_phrase: {
|
||||
action_id: actionId,
|
||||
},
|
||||
},
|
||||
{
|
||||
match_phrase: {
|
||||
response_id: responseId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const aggsResponse = await aggsSearchSource.fetch$().toPromise();
|
||||
|
||||
return {
|
||||
'@timestamp': lastResultsResponse.rawResponse?.hits?.hits[0]?.fields?.['@timestamp'],
|
||||
// @ts-expect-error update types
|
||||
uniqueAgentsCount: aggsResponse.rawResponse.aggregations?.unique_agents?.value,
|
||||
docCount: aggsResponse.rawResponse?.hits?.total,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
enabled: !!(!skip && actionId && interval && agentIds?.length),
|
||||
// @ts-expect-error update types
|
||||
select: (response) => response.rawResponse.aggregations?.runs?.buckets[0] ?? [],
|
||||
enabled: !!(!skip && actionId && interval && agentIds?.length && logsIndexPattern),
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { OSQUERY_INTEGRATION_NAME, PLUGIN_ID } from '../../../common';
|
|||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.get(
|
||||
{
|
||||
|
@ -20,23 +19,26 @@ export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryApp
|
|||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
const privileges = (
|
||||
await esClient.security.hasPrivileges({
|
||||
body: {
|
||||
index: [
|
||||
{
|
||||
names: [`logs-${OSQUERY_INTEGRATION_NAME}.result*`],
|
||||
privileges: ['read'],
|
||||
},
|
||||
],
|
||||
if (osqueryContext.security.authz.mode.useRbacForRequest(request)) {
|
||||
const checkPrivileges = osqueryContext.security.authz.checkPrivilegesDynamicallyWithRequest(
|
||||
request
|
||||
);
|
||||
const { hasAllRequested } = await checkPrivileges({
|
||||
elasticsearch: {
|
||||
cluster: [],
|
||||
index: {
|
||||
[`logs-${OSQUERY_INTEGRATION_NAME}.result*`]: ['read'],
|
||||
},
|
||||
},
|
||||
})
|
||||
).body;
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: `${hasAllRequested}`,
|
||||
});
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
body: privileges,
|
||||
body: 'true',
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -23399,10 +23399,10 @@ react-popper@^2.2.4:
|
|||
react-fast-compare "^3.0.1"
|
||||
warning "^4.0.2"
|
||||
|
||||
react-query@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.21.0.tgz#2e099a7906c38eeeb750e8b9b12121a21fa8d9ef"
|
||||
integrity sha512-5rY5J8OD9f4EdkytjSsdCO+pqbJWKwSIMETfh/UyxqyjLURHE0IhlB+IPNPrzzu/dzK0rRxi5p0IkcCdSfizDQ==
|
||||
react-query@^3.21.1:
|
||||
version "3.21.1"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.21.1.tgz#8fe4df90bf6c6a93e0552ea9baff211d1b28f6e0"
|
||||
integrity sha512-aKFLfNJc/m21JBXJk7sR9tDUYPjotWA4EHAKvbZ++GgxaY+eI0tqBxXmGBuJo0Pisis1W4pZWlZgoRv9yE8yjA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
broadcast-channel "^3.4.1"
|
||||
|
|
Loading…
Reference in a new issue