* add alert on detections * review I + fix unit test * review II * review III * review IV + bug fixes found during review * review VI
This commit is contained in:
parent
f411d2f65e
commit
c62349689e
|
@ -16,11 +16,11 @@ import { MatrixHistogramGqlQuery } from '../../containers/matrix_histogram/index
|
|||
const ID = 'alertsOverTimeQuery';
|
||||
export const alertsStackByOptions: MatrixHistogramOption[] = [
|
||||
{
|
||||
text: i18n.CATEGORY,
|
||||
text: 'event.category',
|
||||
value: 'event.category',
|
||||
},
|
||||
{
|
||||
text: i18n.MODULE,
|
||||
text: 'event.module',
|
||||
value: 'event.module',
|
||||
},
|
||||
];
|
||||
|
@ -54,7 +54,6 @@ export const AlertsView = ({
|
|||
<>
|
||||
<MatrixHistogramContainer
|
||||
dataKey={dataKey}
|
||||
deleteQuery={deleteQuery}
|
||||
defaultStackByOption={alertsStackByOptions[1]}
|
||||
endDate={endDate}
|
||||
errorMessage={i18n.ERROR_FETCHING_ALERTS_DATA}
|
||||
|
@ -68,7 +67,7 @@ export const AlertsView = ({
|
|||
stackByOptions={alertsStackByOptions}
|
||||
startDate={startDate}
|
||||
subtitle={getSubtitle}
|
||||
title={i18n.ALERTS_DOCUMENT_TYPE}
|
||||
title={i18n.ALERTS_GRAPH_TITLE}
|
||||
type={type}
|
||||
updateDateRange={updateDateRange}
|
||||
/>
|
||||
|
|
|
@ -14,10 +14,14 @@ export const TOTAL_COUNT_OF_ALERTS = i18n.translate('xpack.siem.alertsView.total
|
|||
defaultMessage: 'alerts match the search criteria',
|
||||
});
|
||||
|
||||
export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsDocumentType', {
|
||||
export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsTableTitle', {
|
||||
defaultMessage: 'Alerts',
|
||||
});
|
||||
|
||||
export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.alertsView.alertsGraphTitle', {
|
||||
defaultMessage: 'Alert detection frequency',
|
||||
});
|
||||
|
||||
export const ALERTS_STACK_BY_MODULE = i18n.translate(
|
||||
'xpack.siem.alertsView.alertsStackByOptions.module',
|
||||
{
|
||||
|
|
|
@ -20,6 +20,7 @@ import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_ho
|
|||
import { RedirectToNetworkPage } from './redirect_to_network';
|
||||
import { RedirectToOverviewPage } from './redirect_to_overview';
|
||||
import { RedirectToTimelinesPage } from './redirect_to_timelines';
|
||||
import { DetectionEngineTab } from '../../pages/detection_engine/types';
|
||||
|
||||
interface LinkToPageProps {
|
||||
match: RouteMatch<{}>;
|
||||
|
@ -63,6 +64,12 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
|
|||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})`}
|
||||
strict
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToDetectionEnginePage}
|
||||
exact
|
||||
path={`${match.url}/:pageName(${SiemPageName.detectionEngine})/:tabName(${DetectionEngineTab.alerts}|${DetectionEngineTab.signals})`}
|
||||
strict
|
||||
/>
|
||||
<Route
|
||||
component={RedirectToRulesPage}
|
||||
exact
|
||||
|
|
|
@ -7,19 +7,28 @@
|
|||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { DetectionEngineTab } from '../../pages/detection_engine/types';
|
||||
import { RedirectWrapper } from './redirect_wrapper';
|
||||
|
||||
export type DetectionEngineComponentProps = RouteComponentProps<{
|
||||
tabName: DetectionEngineTab;
|
||||
search: string;
|
||||
}>;
|
||||
|
||||
export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine';
|
||||
|
||||
export const RedirectToDetectionEnginePage = ({
|
||||
match: {
|
||||
params: { tabName },
|
||||
},
|
||||
location: { search },
|
||||
}: DetectionEngineComponentProps) => (
|
||||
<RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}${search}`} />
|
||||
);
|
||||
}: DetectionEngineComponentProps) => {
|
||||
const defaultSelectedTab = DetectionEngineTab.signals;
|
||||
const selectedTab = tabName ? tabName : defaultSelectedTab;
|
||||
const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`;
|
||||
|
||||
return <RedirectWrapper to={to} />;
|
||||
};
|
||||
|
||||
export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => {
|
||||
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules${search}`} />;
|
||||
|
@ -28,7 +37,7 @@ export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineCom
|
|||
export const RedirectToCreateRulePage = ({
|
||||
location: { search },
|
||||
}: DetectionEngineComponentProps) => {
|
||||
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule${search}`} />;
|
||||
return <RedirectWrapper to={`/${DETECTION_ENGINE_PAGE_NAME}/rules/create${search}`} />;
|
||||
};
|
||||
|
||||
export const RedirectToRuleDetailsPage = ({
|
||||
|
@ -44,6 +53,8 @@ export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngine
|
|||
};
|
||||
|
||||
export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`;
|
||||
export const getDetectionEngineAlertUrl = () =>
|
||||
`#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`;
|
||||
export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`;
|
||||
export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`;
|
||||
export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`;
|
||||
|
|
|
@ -46,12 +46,12 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
|
|||
isDnsHistogram,
|
||||
isEventsHistogram,
|
||||
isInspected,
|
||||
legendPosition,
|
||||
legendPosition = 'right',
|
||||
mapping,
|
||||
query,
|
||||
scaleType = ScaleType.Time,
|
||||
setQuery,
|
||||
showLegend,
|
||||
showLegend = true,
|
||||
skip,
|
||||
stackByOptions,
|
||||
startDate,
|
||||
|
@ -151,6 +151,7 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramProps &
|
|||
isInspected,
|
||||
loading,
|
||||
data,
|
||||
refetch,
|
||||
]);
|
||||
|
||||
return !hideHistogram ? (
|
||||
|
|
|
@ -39,6 +39,14 @@ export const AnomaliesQueryTabBody = ({
|
|||
flowTarget,
|
||||
ip,
|
||||
}: AnomaliesQueryTabBodyProps) => {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (deleteQuery) {
|
||||
deleteQuery({ id: ID });
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [, siemJobs] = useSiemJobs(true);
|
||||
const [anomalyScore] = useUiSetting$<number>(DEFAULT_ANOMALY_SCORE);
|
||||
|
||||
|
@ -51,21 +59,12 @@ export const AnomaliesQueryTabBody = ({
|
|||
ip
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (deleteQuery) {
|
||||
deleteQuery({ id: ID });
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MatrixHistogramContainer
|
||||
isAnomaliesHistogram={true}
|
||||
dataKey="AnomaliesHistogram"
|
||||
defaultStackByOption={anomaliesStackByOptions[0]}
|
||||
deleteQuery={deleteQuery}
|
||||
endDate={endDate}
|
||||
errorMessage={i18n.ERROR_FETCHING_ANOMALIES_DATA}
|
||||
filterQuery={mergedFilterQuery}
|
||||
|
|
|
@ -26,7 +26,6 @@ import { SetQuery } from '../../pages/hosts/navigation/types';
|
|||
export interface OwnProps extends QueryTemplateProps {
|
||||
dataKey: string | string[];
|
||||
defaultStackByOption: MatrixHistogramOption;
|
||||
deleteQuery?: ({ id }: { id: string }) => void;
|
||||
errorMessage: string;
|
||||
headerChildren?: React.ReactNode;
|
||||
hideHistogramIfEmpty?: boolean;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { getOr } from 'lodash/fp';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
MatrixHistogramDataTypes,
|
||||
MatrixHistogramQueryProps,
|
||||
|
@ -35,7 +35,7 @@ export const useQuery = <Hit, Aggs, TCache = object>({
|
|||
}: MatrixHistogramQueryProps) => {
|
||||
const [defaultIndex] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY);
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
const [refetch, setRefetch] = useState<inputsModel.Refetch>();
|
||||
const refetch = useRef<inputsModel.Refetch>();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [data, setData] = useState<MatrixHistogramDataTypes[] | null>(null);
|
||||
const [inspect, setInspect] = useState<inputsModel.InspectQuery | null>(null);
|
||||
|
@ -71,7 +71,7 @@ export const useQuery = <Hit, Aggs, TCache = object>({
|
|||
return apolloClient
|
||||
.query<GetMatrixHistogramQuery.Query, GetMatrixHistogramQuery.Variables>({
|
||||
query,
|
||||
fetchPolicy: 'cache-first',
|
||||
fetchPolicy: 'network-only',
|
||||
variables: matrixHistogramVariables,
|
||||
context: {
|
||||
fetchOptions: {
|
||||
|
@ -103,9 +103,7 @@ export const useQuery = <Hit, Aggs, TCache = object>({
|
|||
}
|
||||
);
|
||||
}
|
||||
setRefetch(() => {
|
||||
fetchData();
|
||||
});
|
||||
refetch.current = fetchData;
|
||||
fetchData();
|
||||
return () => {
|
||||
isSubscribed = false;
|
||||
|
@ -122,5 +120,5 @@ export const useQuery = <Hit, Aggs, TCache = object>({
|
|||
endDate,
|
||||
]);
|
||||
|
||||
return { data, loading, inspect, totalCount, refetch };
|
||||
return { data, loading, inspect, totalCount, refetch: refetch.current };
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle',
|
|||
});
|
||||
|
||||
export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', {
|
||||
defaultMessage: 'All signals',
|
||||
defaultMessage: 'Signals',
|
||||
});
|
||||
|
||||
export const SIGNALS_DOCUMENT_TYPE = i18n.translate(
|
||||
|
|
|
@ -4,18 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { SignalsHistogramOption } from './types';
|
||||
|
||||
export const signalsHistogramOptions: SignalsHistogramOption[] = [
|
||||
{ text: i18n.STACK_BY_RISK_SCORES, value: 'signal.rule.risk_score' },
|
||||
{ text: i18n.STACK_BY_SEVERITIES, value: 'signal.rule.severity' },
|
||||
{ text: i18n.STACK_BY_DESTINATION_IPS, value: 'destination.ip' },
|
||||
{ text: i18n.STACK_BY_ACTIONS, value: 'event.action' },
|
||||
{ text: i18n.STACK_BY_CATEGORIES, value: 'event.category' },
|
||||
{ text: i18n.STACK_BY_HOST_NAMES, value: 'host.name' },
|
||||
{ text: i18n.STACK_BY_RULE_TYPES, value: 'signal.rule.type' },
|
||||
{ text: i18n.STACK_BY_RULE_NAMES, value: 'signal.rule.name' },
|
||||
{ text: i18n.STACK_BY_SOURCE_IPS, value: 'source.ip' },
|
||||
{ text: i18n.STACK_BY_USERS, value: 'user.name' },
|
||||
{ text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' },
|
||||
{ text: 'signal.rule.severity', value: 'signal.rule.severity' },
|
||||
{ text: 'destination.ip', value: 'destination.ip' },
|
||||
{ text: 'event.action', value: 'event.action' },
|
||||
{ text: 'event.category', value: 'event.category' },
|
||||
{ text: 'host.name', value: 'host.name' },
|
||||
{ text: 'signal.rule.type', value: 'signal.rule.type' },
|
||||
{ text: 'signal.rule.name', value: 'signal.rule.name' },
|
||||
{ text: 'source.ip', value: 'source.ip' },
|
||||
{ text: 'user.name', value: 'user.name' },
|
||||
];
|
||||
|
|
|
@ -46,7 +46,7 @@ export const SignalsHistogramPanel = memo<SignalsHistogramPanelProps>(
|
|||
filters,
|
||||
query,
|
||||
from,
|
||||
legendPosition = 'bottom',
|
||||
legendPosition = 'right',
|
||||
loadingInitial = false,
|
||||
showLinkToSignals = false,
|
||||
showTotalSignalsCount = false,
|
||||
|
|
|
@ -44,7 +44,7 @@ export const SignalsHistogram = React.memo<HistogramSignalsProps>(
|
|||
from,
|
||||
query,
|
||||
filters,
|
||||
legendPosition = 'bottom',
|
||||
legendPosition = 'right',
|
||||
loadingInitial,
|
||||
setTotalSignalsCount,
|
||||
stackByField,
|
||||
|
|
|
@ -4,27 +4,31 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiButton, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { StickyContainer } from 'react-sticky';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { SiemSearchBar } from '../../components/search_bar';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { GlobalTime } from '../../containers/global_time';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
|
||||
import { Query } from '../../../../../../../src/plugins/data/common/query';
|
||||
import { esFilters } from '../../../../../../../src/plugins/data/common/es_query';
|
||||
|
||||
import { GlobalTime } from '../../containers/global_time';
|
||||
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
|
||||
import { AlertsTable } from '../../components/alerts_viewer/alerts_table';
|
||||
import { FiltersGlobal } from '../../components/filters_global';
|
||||
import { HeaderPage } from '../../components/header_page';
|
||||
import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine';
|
||||
import { SiemSearchBar } from '../../components/search_bar';
|
||||
import { WrapperPage } from '../../components/wrapper_page';
|
||||
import { State } from '../../store';
|
||||
import { inputsSelectors } from '../../store/inputs';
|
||||
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
|
||||
import { SpyRoute } from '../../utils/route/spy_routes';
|
||||
import { InputsModelId } from '../../store/inputs/constants';
|
||||
import { InputsRange } from '../../store/inputs/model';
|
||||
import { AlertsByCategory } from '../overview/alerts_by_category';
|
||||
import { useSignalInfo } from './components/signals_info';
|
||||
import { SignalsTable } from './components/signals';
|
||||
import { NoWriteSignalsCallOut } from './components/no_write_signals_callout';
|
||||
|
@ -35,6 +39,7 @@ import { DetectionEngineEmptyPage } from './detection_engine_empty_page';
|
|||
import { DetectionEngineNoIndex } from './detection_engine_no_signal_index';
|
||||
import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated';
|
||||
import * as i18n from './translations';
|
||||
import { DetectionEngineTab } from './types';
|
||||
|
||||
interface ReduxProps {
|
||||
filters: esFilters.Filter[];
|
||||
|
@ -51,8 +56,22 @@ export interface DispatchProps {
|
|||
|
||||
type DetectionEngineComponentProps = ReduxProps & DispatchProps;
|
||||
|
||||
const detectionsTabs = [
|
||||
{
|
||||
id: DetectionEngineTab.signals,
|
||||
name: i18n.SIGNAL,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: DetectionEngineTab.alerts,
|
||||
name: i18n.ALERT,
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
|
||||
({ filters, query, setAbsoluteRangeDatePicker }) => {
|
||||
const { tabName = DetectionEngineTab.signals } = useParams();
|
||||
const {
|
||||
loading,
|
||||
isSignalIndexExists,
|
||||
|
@ -87,6 +106,25 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
|
|||
</WrapperPage>
|
||||
);
|
||||
}
|
||||
|
||||
const tabs = useMemo(
|
||||
() => (
|
||||
<EuiTabs>
|
||||
{detectionsTabs.map(tab => (
|
||||
<EuiTab
|
||||
isSelected={tab.id === tabName}
|
||||
disabled={tab.disabled}
|
||||
key={tab.id}
|
||||
href={`#/${DETECTION_ENGINE_PAGE_NAME}/${tab.id}`}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
),
|
||||
[detectionsTabs, tabName]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasIndexWrite != null && !hasIndexWrite && <NoWriteSignalsCallOut />}
|
||||
|
@ -99,7 +137,6 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
|
|||
</FiltersGlobal>
|
||||
<WrapperPage>
|
||||
<HeaderPage
|
||||
border
|
||||
subtitle={
|
||||
lastSignals != null && (
|
||||
<>
|
||||
|
@ -117,26 +154,49 @@ const DetectionEngineComponent = React.memo<DetectionEngineComponentProps>(
|
|||
</HeaderPage>
|
||||
|
||||
<GlobalTime>
|
||||
{({ to, from }) => (
|
||||
{({ to, from, deleteQuery, setQuery }) => (
|
||||
<>
|
||||
<SignalsHistogramPanel
|
||||
filters={filters}
|
||||
from={from}
|
||||
loadingInitial={loading}
|
||||
query={query}
|
||||
stackByOptions={signalsHistogramOptions}
|
||||
to={to}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
/>
|
||||
{tabs}
|
||||
<EuiSpacer />
|
||||
<SignalsTable
|
||||
loading={loading}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
canUserCRUD={canUserCRUD ?? false}
|
||||
from={from}
|
||||
signalsIndex={signalIndexName ?? ''}
|
||||
to={to}
|
||||
/>
|
||||
{tabName === DetectionEngineTab.signals && (
|
||||
<>
|
||||
<SignalsHistogramPanel
|
||||
filters={filters}
|
||||
from={from}
|
||||
loadingInitial={loading}
|
||||
query={query}
|
||||
stackByOptions={signalsHistogramOptions}
|
||||
to={to}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<SignalsTable
|
||||
loading={loading}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
canUserCRUD={canUserCRUD ?? false}
|
||||
from={from}
|
||||
signalsIndex={signalIndexName ?? ''}
|
||||
to={to}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{tabName === DetectionEngineTab.alerts && (
|
||||
<>
|
||||
<AlertsByCategory
|
||||
deleteQuery={deleteQuery}
|
||||
filters={filters}
|
||||
from={from}
|
||||
hideHeaderChildren={true}
|
||||
indexPattern={indexPattern}
|
||||
query={query}
|
||||
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker!}
|
||||
setQuery={setQuery}
|
||||
to={to}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<AlertsTable endDate={to} startDate={from} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</GlobalTime>
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
import React from 'react';
|
||||
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { ManageUserInfo } from './components/user_info';
|
||||
import { CreateRuleComponent } from './rules/create';
|
||||
import { DetectionEngine } from './detection_engine';
|
||||
import { EditRuleComponent } from './rules/edit';
|
||||
import { RuleDetails } from './rules/details';
|
||||
import { RulesComponent } from './rules';
|
||||
import { ManageUserInfo } from './components/user_info';
|
||||
import { DetectionEngineTab } from './types';
|
||||
|
||||
const detectionEnginePath = `/:pageName(detection-engine)`;
|
||||
|
||||
|
@ -21,7 +22,11 @@ type Props = Partial<RouteComponentProps<{}>> & { url: string };
|
|||
export const DetectionEngineContainer = React.memo<Props>(() => (
|
||||
<ManageUserInfo>
|
||||
<Switch>
|
||||
<Route exact path={detectionEnginePath} strict>
|
||||
<Route
|
||||
exact
|
||||
path={`${detectionEnginePath}/:tabName(${DetectionEngineTab.signals}|${DetectionEngineTab.alerts})`}
|
||||
strict
|
||||
>
|
||||
<DetectionEngine />
|
||||
</Route>
|
||||
<Route exact path={`${detectionEnginePath}/rules`}>
|
||||
|
@ -30,7 +35,7 @@ export const DetectionEngineContainer = React.memo<Props>(() => (
|
|||
<Route exact path={`${detectionEnginePath}/rules/create`}>
|
||||
<CreateRuleComponent />
|
||||
</Route>
|
||||
<Route exact path={`${detectionEnginePath}/rules/id/:ruleId`}>
|
||||
<Route exact path={`${detectionEnginePath}/rules/id/:ruleId/`}>
|
||||
<RuleDetails />
|
||||
</Route>
|
||||
<Route exact path={`${detectionEnginePath}/rules/id/:ruleId/edit`}>
|
||||
|
@ -39,7 +44,10 @@ export const DetectionEngineContainer = React.memo<Props>(() => (
|
|||
<Route
|
||||
path="/detection-engine/"
|
||||
render={({ location: { search = '' } }) => (
|
||||
<Redirect from="/detection-engine/" to={`/detection-engine${search}`} />
|
||||
<Redirect
|
||||
from="/detection-engine/"
|
||||
to={`/detection-engine/${DetectionEngineTab.signals}${search}`}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
EuiSpacer,
|
||||
EuiHealth,
|
||||
EuiTab,
|
||||
EuiTabs,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
|
@ -78,14 +79,19 @@ export interface DispatchProps {
|
|||
}>;
|
||||
}
|
||||
|
||||
enum RuleDetailTabs {
|
||||
signals = 'signals',
|
||||
failures = 'failures',
|
||||
}
|
||||
|
||||
const ruleDetailTabs = [
|
||||
{
|
||||
id: 'signal',
|
||||
id: RuleDetailTabs.signals,
|
||||
name: detectionI18n.SIGNAL,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: 'failure',
|
||||
id: RuleDetailTabs.failures,
|
||||
name: i18n.FAILURE_HISTORY_TAB,
|
||||
disabled: false,
|
||||
},
|
||||
|
@ -106,7 +112,7 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
|
|||
} = useUserInfo();
|
||||
const { ruleId } = useParams();
|
||||
const [isLoading, rule] = useRule(ruleId);
|
||||
const [ruleDetailTab, setRuleDetailTab] = useState('signal');
|
||||
const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals);
|
||||
const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({
|
||||
rule,
|
||||
detailsView: true,
|
||||
|
@ -187,22 +193,27 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
|
|||
: 'subdued';
|
||||
|
||||
const tabs = useMemo(
|
||||
() =>
|
||||
ruleDetailTabs.map(tab => (
|
||||
<EuiTab
|
||||
onClick={() => setRuleDetailTab(tab.id)}
|
||||
isSelected={tab.id === ruleDetailTab}
|
||||
disabled={tab.disabled}
|
||||
key={tab.name}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
)),
|
||||
() => (
|
||||
<EuiTabs>
|
||||
{ruleDetailTabs.map(tab => (
|
||||
<EuiTab
|
||||
onClick={() => setRuleDetailTab(tab.id)}
|
||||
isSelected={tab.id === ruleDetailTab}
|
||||
disabled={tab.disabled}
|
||||
key={tab.id}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
),
|
||||
[ruleDetailTabs, ruleDetailTab, setRuleDetailTab]
|
||||
);
|
||||
const ruleError = useMemo(
|
||||
() =>
|
||||
rule?.status === 'failed' && ruleDetailTab === 'signal' && rule?.last_failure_at != null ? (
|
||||
rule?.status === 'failed' &&
|
||||
ruleDetailTab === RuleDetailTabs.signals &&
|
||||
rule?.last_failure_at != null ? (
|
||||
<RuleStatusFailedCallOut
|
||||
message={rule?.last_failure_message ?? ''}
|
||||
date={rule?.last_failure_at}
|
||||
|
@ -316,7 +327,7 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
|
|||
{ruleError}
|
||||
{tabs}
|
||||
<EuiSpacer />
|
||||
{ruleDetailTab === 'signal' && (
|
||||
{ruleDetailTab === RuleDetailTabs.signals && (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem component="section" grow={1}>
|
||||
|
@ -381,7 +392,9 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
{ruleDetailTab === 'failure' && <FailureHistory id={rule?.id} />}
|
||||
{ruleDetailTab === RuleDetailTabs.failures && (
|
||||
<FailureHistory id={rule?.id} />
|
||||
)}
|
||||
</WrapperPage>
|
||||
</StickyContainer>
|
||||
)}
|
||||
|
|
|
@ -22,7 +22,7 @@ export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.add
|
|||
});
|
||||
|
||||
export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', {
|
||||
defaultMessage: 'Rules',
|
||||
defaultMessage: 'Signal detection rules',
|
||||
});
|
||||
|
||||
export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', {
|
||||
|
|
|
@ -22,8 +22,12 @@ export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', {
|
|||
defaultMessage: 'Signals',
|
||||
});
|
||||
|
||||
export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', {
|
||||
defaultMessage: 'Third-party alerts',
|
||||
});
|
||||
|
||||
export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', {
|
||||
defaultMessage: 'Manage rules',
|
||||
defaultMessage: 'Manage signal detection rules',
|
||||
});
|
||||
|
||||
export const PANEL_SUBTITLE_SHOWING = i18n.translate(
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export enum DetectionEngineTab {
|
||||
signals = 'signals',
|
||||
alerts = 'alerts',
|
||||
}
|
|
@ -25,7 +25,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable);
|
|||
const ID = 'authenticationsOverTimeQuery';
|
||||
const authStackByOptions: MatrixHistogramOption[] = [
|
||||
{
|
||||
text: i18n.NAVIGATION_AUTHENTICATIONS_STACK_BY_EVENT_TYPE,
|
||||
text: 'event.type',
|
||||
value: 'event.type',
|
||||
},
|
||||
];
|
||||
|
@ -71,7 +71,6 @@ export const AuthenticationsQueryTabBody = ({
|
|||
isAuthenticationsHistogram={true}
|
||||
dataKey="AuthenticationsHistogram"
|
||||
defaultStackByOption={authStackByOptions[0]}
|
||||
deleteQuery={deleteQuery}
|
||||
endDate={endDate}
|
||||
errorMessage={i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA}
|
||||
filterQuery={filterQuery}
|
||||
|
|
|
@ -20,11 +20,11 @@ const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
|
|||
|
||||
export const eventsStackByOptions: MatrixHistogramOption[] = [
|
||||
{
|
||||
text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION,
|
||||
text: 'event.action',
|
||||
value: 'event.action',
|
||||
},
|
||||
{
|
||||
text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET,
|
||||
text: 'event.dataset',
|
||||
value: 'event.dataset',
|
||||
},
|
||||
];
|
||||
|
@ -50,7 +50,6 @@ export const EventsQueryTabBody = ({
|
|||
<MatrixHistogramContainer
|
||||
dataKey="EventsHistogram"
|
||||
defaultStackByOption={eventsStackByOptions[0]}
|
||||
deleteQuery={deleteQuery}
|
||||
endDate={endDate}
|
||||
isEventsHistogram={true}
|
||||
errorMessage={i18n.ERROR_FETCHING_EVENTS_DATA}
|
||||
|
|
|
@ -28,13 +28,6 @@ export const NAVIGATION_AUTHENTICATIONS_TITLE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const NAVIGATION_AUTHENTICATIONS_STACK_BY_EVENT_TYPE = i18n.translate(
|
||||
'xpack.siem.hosts.navigation.authentications.stackByEventType',
|
||||
{
|
||||
defaultMessage: 'event type',
|
||||
}
|
||||
);
|
||||
|
||||
export const NAVIGATION_UNCOMMON_PROCESSES_TITLE = i18n.translate(
|
||||
'xpack.siem.hosts.navigation.uncommonProcessesTitle',
|
||||
{
|
||||
|
@ -53,20 +46,6 @@ export const NAVIGATION_EVENTS_TITLE = i18n.translate('xpack.siem.hosts.navigati
|
|||
defaultMessage: 'Events',
|
||||
});
|
||||
|
||||
export const NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION = i18n.translate(
|
||||
'xpack.siem.hosts.navigation.eventsStackByEventAction',
|
||||
{
|
||||
defaultMessage: 'action',
|
||||
}
|
||||
);
|
||||
|
||||
export const NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET = i18n.translate(
|
||||
'xpack.siem.hosts.navigation.eventsStackByEventDataset',
|
||||
{
|
||||
defaultMessage: 'dataset',
|
||||
}
|
||||
);
|
||||
|
||||
export const NAVIGATION_ALERTS_TITLE = i18n.translate('xpack.siem.hosts.navigation.alertsTitle', {
|
||||
defaultMessage: 'Alerts',
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@ const NetworkDnsTableManage = manageQuery(NetworkDnsTable);
|
|||
|
||||
const dnsStackByOptions: MatrixHistogramOption[] = [
|
||||
{
|
||||
text: i18n.STACK_BY_DOMAIN,
|
||||
text: 'dns.question.registered_domain',
|
||||
value: 'dns.question.registered_domain',
|
||||
},
|
||||
];
|
||||
|
@ -70,7 +70,6 @@ export const DnsQueryTabBody = ({
|
|||
title={getTitle}
|
||||
type={networkModel.NetworkType.page}
|
||||
updateDateRange={updateDateRange}
|
||||
showLegend={false}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<NetworkDnsQuery
|
||||
|
|
|
@ -22,10 +22,6 @@ export const NAVIGATION_DNS_TITLE = i18n.translate('xpack.siem.network.navigatio
|
|||
defaultMessage: 'DNS',
|
||||
});
|
||||
|
||||
export const STACK_BY_DOMAIN = i18n.translate('xpack.siem.hosts.dns.stackByDomain', {
|
||||
defaultMessage: 'unique domains',
|
||||
});
|
||||
|
||||
export const ERROR_FETCHING_DNS_DATA = i18n.translate(
|
||||
'xpack.siem.hosts.navigation.dns.histogram.errorFetchingDnsData',
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -16,16 +16,15 @@ import {
|
|||
UNIT,
|
||||
} from '../../../components/alerts_viewer/translations';
|
||||
import { alertsStackByOptions } from '../../../components/alerts_viewer';
|
||||
import { getTabsOnHostsUrl } from '../../../components/link_to/redirect_to_hosts';
|
||||
import { getDetectionEngineAlertUrl } from '../../../components/link_to/redirect_to_detection_engine';
|
||||
import { MatrixHistogramContainer } from '../../../containers/matrix_histogram';
|
||||
import { MatrixHistogramGqlQuery } from '../../../containers/matrix_histogram/index.gql_query';
|
||||
import { MatrixHistogramOption } from '../../../components/matrix_histogram/types';
|
||||
import { useKibana, useUiSetting$ } from '../../../lib/kibana';
|
||||
import { convertToBuildEsQuery } from '../../../lib/keury';
|
||||
import { SetAbsoluteRangeDatePicker } from '../../network/types';
|
||||
import { esQuery } from '../../../../../../../../src/plugins/data/public';
|
||||
import { inputsModel } from '../../../store';
|
||||
import { HostsTableType, HostsType } from '../../../store/hosts/model';
|
||||
import { HostsType } from '../../../store/hosts/model';
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
@ -39,6 +38,7 @@ interface Props {
|
|||
deleteQuery?: ({ id }: { id: string }) => void;
|
||||
filters?: esFilters.Filter[];
|
||||
from: number;
|
||||
hideHeaderChildren?: boolean;
|
||||
indexPattern: IIndexPattern;
|
||||
query?: Query;
|
||||
setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker;
|
||||
|
@ -60,14 +60,24 @@ export const AlertsByCategory = React.memo<Props>(
|
|||
deleteQuery,
|
||||
filters = NO_FILTERS,
|
||||
from,
|
||||
hideHeaderChildren = false,
|
||||
indexPattern,
|
||||
query = DEFAULT_QUERY,
|
||||
setAbsoluteRangeDatePicker,
|
||||
setQuery,
|
||||
to,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (deleteQuery) {
|
||||
deleteQuery({ id: ID });
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const kibana = useKibana();
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
|
||||
const updateDateRangeCallback = useCallback(
|
||||
(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
|
||||
|
@ -76,17 +86,11 @@ export const AlertsByCategory = React.memo<Props>(
|
|||
);
|
||||
const alertsCountViewAlertsButton = useMemo(
|
||||
() => (
|
||||
<ViewAlertsButton href={getTabsOnHostsUrl(HostsTableType.alerts)}>
|
||||
{i18n.VIEW_ALERTS}
|
||||
</ViewAlertsButton>
|
||||
<ViewAlertsButton href={getDetectionEngineAlertUrl()}>{i18n.VIEW_ALERTS}</ViewAlertsButton>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const getTitle = useCallback(
|
||||
(option: MatrixHistogramOption) => i18n.ALERTS_COUNT_BY(option.text),
|
||||
[]
|
||||
);
|
||||
const getSubtitle = useCallback(
|
||||
(totalCount: number) =>
|
||||
`${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`,
|
||||
|
@ -96,7 +100,6 @@ export const AlertsByCategory = React.memo<Props>(
|
|||
return (
|
||||
<MatrixHistogramContainer
|
||||
dataKey="AlertsHistogram"
|
||||
deleteQuery={deleteQuery}
|
||||
defaultStackByOption={alertsStackByOptions[0]}
|
||||
endDate={to}
|
||||
errorMessage={ERROR_FETCHING_ALERTS_DATA}
|
||||
|
@ -106,7 +109,7 @@ export const AlertsByCategory = React.memo<Props>(
|
|||
queries: [query],
|
||||
filters,
|
||||
})}
|
||||
headerChildren={alertsCountViewAlertsButton}
|
||||
headerChildren={hideHeaderChildren ? null : alertsCountViewAlertsButton}
|
||||
id={ID}
|
||||
isAlertsHistogram={true}
|
||||
legendPosition={'right'}
|
||||
|
@ -115,7 +118,7 @@ export const AlertsByCategory = React.memo<Props>(
|
|||
sourceId="default"
|
||||
stackByOptions={alertsStackByOptions}
|
||||
startDate={from}
|
||||
title={getTitle}
|
||||
title={i18n.ALERTS_GRAPH_TITLE}
|
||||
subtitle={getSubtitle}
|
||||
type={HostsType.page}
|
||||
updateDateRange={updateDateRangeCallback}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -66,8 +66,17 @@ export const EventsByDataset = React.memo<Props>(
|
|||
setQuery,
|
||||
to,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (deleteQuery) {
|
||||
deleteQuery({ id: ID });
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const kibana = useKibana();
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
|
||||
const updateDateRangeCallback = useCallback(
|
||||
(min: number, max: number) => {
|
||||
setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max });
|
||||
|
@ -96,7 +105,6 @@ export const EventsByDataset = React.memo<Props>(
|
|||
return (
|
||||
<MatrixHistogramContainer
|
||||
dataKey="EventsHistogram"
|
||||
deleteQuery={deleteQuery}
|
||||
defaultStackByOption={eventsStackByOptions[1]}
|
||||
endDate={to}
|
||||
errorMessage={ERROR_FETCHING_EVENTS_DATA}
|
||||
|
|
|
@ -12,6 +12,10 @@ export const ALERTS_COUNT_BY = (groupByField: string) =>
|
|||
defaultMessage: 'Alerts count by {groupByField}',
|
||||
});
|
||||
|
||||
export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.overview.alertsGraphTitle', {
|
||||
defaultMessage: 'Alert detection frequency',
|
||||
});
|
||||
|
||||
export const EVENTS_COUNT_BY = (groupByField: string) =>
|
||||
i18n.translate('xpack.siem.overview.eventsCountByTitle', {
|
||||
values: { groupByField },
|
||||
|
|
Loading…
Reference in a new issue