Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Remove discover] Implement embeddable dashboard on Threat Hunting module #6486

Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2b6e8f0
Migrated visualizations to embeddables
jbiset Mar 6, 2024
07776d3
Added withPinnedAgent HOC
jbiset Mar 7, 2024
ba0e78a
Added dashboard update mechanism depending on whether or not an agent…
jbiset Mar 7, 2024
978d3a5
Pinned agent visualization definitions are migrated and aesthetic adj…
jbiset Mar 8, 2024
871352c
The interaction was added to the KPIs, the links to the lower table w…
jbiset Mar 13, 2024
64e1ce1
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Mar 13, 2024
66af0cf
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Mar 15, 2024
b43f524
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Apr 18, 2024
a0c8360
Integrated new data source on Threat Hunting module
jbiset Apr 18, 2024
3bef252
DiscoverNoResults and LoadingSpinner components are replaced with com…
jbiset Apr 19, 2024
af1bb88
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Apr 22, 2024
5c6ff8b
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Apr 22, 2024
d110c86
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Apr 22, 2024
30d8ffd
Clean code and fixed dashboards conditions
jbiset Apr 22, 2024
b411272
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Apr 23, 2024
e25b059
Improved condition for rendering the dashboard and SampleData message
jbiset Apr 23, 2024
cb4fe29
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Apr 23, 2024
18d7bf6
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
jbiset Apr 24, 2024
198c17a
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
yenienserrano Apr 24, 2024
4ac01ff
Removed unnecessary general/threat hunting in tabFilters in common da…
jbiset Apr 24, 2024
c090a53
Removed unused getImplicitPinnedAgent in modules-helper
jbiset Apr 24, 2024
52da83e
Added dateRange param to fetchData in dashboard useEffect, added wz-d…
jbiset Apr 24, 2024
ffa1282
Deleted unnecessary wz-discover class on SearchBar wrapper
jbiset Apr 24, 2024
05419b9
Changed Threat Hunting columns file name
jbiset Apr 24, 2024
e8611aa
Deleted unused imports in modules-helper
jbiset Apr 24, 2024
19f48ef
Merge branch '4.9.0' into 6478-remove-discover-implement-embeddable-d…
asteriscos Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 15 additions & 3 deletions plugins/main/public/components/common/modules/modules-defaults.tsx
Expand Up @@ -27,6 +27,7 @@ import {
} from '../wazuh-discover/wz-discover';
import { threatHuntingColumns } from '../wazuh-discover/config/data-grid-columns';
import { vulnerabilitiesColumns } from '../../overview/vulnerabilities/events/vulnerabilities-columns';
import { DashboardThreatHunting } from '../../overview/threat-hunting/dashboard/dashboard';
import React from 'react';
import { dockerColumns } from '../../overview/docker/events/docker-columns';
import { googleCloudColumns } from '../../overview/google-cloud/events/google-cloud-columns';
Expand All @@ -44,7 +45,10 @@ import { mitreAttackColumns } from '../../overview/mitre/events/mitre-attack-col
import { virustotalColumns } from '../../overview/virustotal/events/virustotal-columns';
import { malwareDetectionColumns } from '../../overview/malware-detection/events/malware-detection-columns';
import { WAZUH_VULNERABILITIES_PATTERN } from '../../../../common/constants';
import { AlertsVulnerabilitiesDataSource } from '../data-source';
import {
AlertsDataSource,
jbiset marked this conversation as resolved.
Show resolved Hide resolved
AlertsVulnerabilitiesDataSource,
} from '../data-source';

const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*';
const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN;
Expand Down Expand Up @@ -81,8 +85,16 @@ export const ModulesDefaults = {
general: {
init: 'events',
tabs: [
DashboardTab,
renderDiscoverTab(DEFAULT_INDEX_PATTERN, threatHuntingColumns),
{
id: 'dashboard',
name: 'Dashboard',
buttons: [ButtonModuleExploreAgent, ButtonModuleGenerateReport],
component: DashboardThreatHunting,
},
renderDiscoverTab({
tableColumns: threatHuntingColumns,
DataSource: AlertsDataSource,
}),
],
availableFor: ['manager', 'agent'],
},
Expand Down
Expand Up @@ -2,6 +2,8 @@ import { getAngularModule, getDataPlugin } from '../../../kibana-services';
import { AppState } from '../../../react-services/app-state';
import { FilterHandler } from '../../../utils/filter-handler';
import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../../../common/constants';
import { useFilterManager } from '../hooks';
import { FilterStateStore } from '../../../../../../src/plugins/data/common';
jbiset marked this conversation as resolved.
Show resolved Hide resolved

export class ModulesHelper {
static async getDiscoverScope() {
Expand Down
@@ -0,0 +1,329 @@
import React, { useState, useEffect, useMemo } from 'react';
import { getPlugins, getWazuhCorePlugin } from '../../../../kibana-services';
import { ViewMode } from '../../../../../../../src/plugins/embeddable/public';
import { SearchResponse } from '../../../../../../../src/core/server';
import { IndexPattern } from '../../../../../../../src/plugins/data/common';
import { getDashboardPanels } from './dashboard_panels';
import { I18nProvider } from '@osd/i18n/react';
import useSearchBar from '../../../common/search-bar/use-search-bar';
import { getKPIsPanel } from './dashboard_panels_kpis';
import {
EuiFlexGroup,
EuiFlexItem,
EuiButtonIcon,
EuiDataGrid,
EuiToolTip,
EuiDataGridCellValueElementProps,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiTitle,
EuiButtonEmpty,
} from '@elastic/eui';
import {
ErrorFactory,
ErrorHandler,
HttpError,
} from '../../../../react-services/error-management';
import {
MAX_ENTRIES_PER_QUERY,
exportSearchToCSV,
} from '../../../common/data-grid/data-grid-service';
import { useDocViewer } from '../../../common/doc-viewer/use-doc-viewer';
import { useDataGrid } from '../../../common/data-grid/use-data-grid';
import { HitsCounter } from '../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter';
import { formatNumWithCommas } from '../../../../kibana-integrations/discover/application/helpers/format_number_with_commas';
import DocViewer from '../../../common/doc-viewer/doc-viewer';
import { withErrorBoundary } from '../../../common/hocs/error-boundary/with-error-boundary';
import './threat_hunting_dashboard.scss';
import { SampleDataWarning } from '../../../visualize/components/sample-data-warning';
import {
threatHuntingTableAgentColumns,
threatHuntingTableDefaultColumns,
} from '../events/threat-hunting-columns';
import {
AlertsDataSource,
AlertsDataSourceRepository,
PatternDataSource,
PatternDataSourceFilterManager,
tParsedIndexPattern,
useDataSource,
} from '../../../common/data-source';
import { DiscoverNoResults } from '../../../common/no-results/no-results';
import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner';

const plugins = getPlugins();

const SearchBar = getPlugins().data.ui.SearchBar;

const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer;

const DashboardTH: React.FC = () => {
const {
filters,
dataSource,
fetchFilters,
isLoading: isDataSourceLoading,
fetchData,
setFilters,
} = useDataSource<tParsedIndexPattern, PatternDataSource>({
DataSource: AlertsDataSource,
repository: new AlertsDataSourceRepository(),
});

const [results, setResults] = useState<SearchResponse>({} as SearchResponse);

const { searchBarProps } = useSearchBar({
indexPattern: dataSource?.indexPattern as IndexPattern,
filters,
setFilters,
});
const { query, dateRangeFrom, dateRangeTo } = searchBarProps;

const [inspectedHit, setInspectedHit] = useState<any>(undefined);
const [isExporting, setIsExporting] = useState<boolean>(false);

const sideNavDocked = getWazuhCorePlugin().hooks.useDockedSideNav();

const onClickInspectDoc = useMemo(
() => (index: number) => {
const rowClicked = results.hits.hits[index];
setInspectedHit(rowClicked);
},
[results],
);

const DocViewInspectButton = ({
rowIndex,
}: EuiDataGridCellValueElementProps) => {
const inspectHintMsg = 'Inspect document details';
return (
<EuiToolTip content={inspectHintMsg}>
<EuiButtonIcon
onClick={() => onClickInspectDoc(rowIndex)}
iconType='inspect'
aria-label={inspectHintMsg}
/>
</EuiToolTip>
);
};

const dataGridProps = useDataGrid({
ariaLabelledBy: 'Threat Hunting Table',
defaultColumns: threatHuntingTableDefaultColumns,
results,
indexPattern: dataSource?.indexPattern,
DocViewInspectButton,
});

const { pagination, sorting, columnVisibility } = dataGridProps;

const docViewerProps = useDocViewer({
doc: inspectedHit,
indexPattern: dataSource?.indexPattern,
});

const pinnedAgent =
PatternDataSourceFilterManager.getPinnedAgentFilter(dataSource?.id!)
.length > 0;

useEffect(() => {
const currentColumns = !pinnedAgent
? threatHuntingTableDefaultColumns
: threatHuntingTableAgentColumns;
columnVisibility.setVisibleColumns(currentColumns.map(({ id }) => id));
}, [pinnedAgent]);

useEffect(() => {
if (isDataSourceLoading) {
return;
}
fetchData({
query,
pagination,
sorting,
dateRange: {
from: dateRangeFrom,
to: dateRangeTo,
},
})
.then(results => {
setResults(results);
})
.catch(error => {
const searchError = ErrorFactory.create(HttpError, {
error,
message: 'Error fetching threat hunting',
});
ErrorHandler.handleError(searchError);
});
}, [
JSON.stringify(fetchFilters),
JSON.stringify(query),
JSON.stringify(pagination),
JSON.stringify(sorting),
dateRangeFrom,
dateRangeTo,
]);

const onClickExportResults = async () => {
const params = {
indexPattern: dataSource?.indexPattern,
filters: fetchFilters ?? [],
query,
fields: columnVisibility.visibleColumns,
pagination: {
pageIndex: 0,
pageSize: results.hits.total,
},
sorting,
};
try {
setIsExporting(true);
await exportSearchToCSV(params);
} catch (error) {
const searchError = ErrorFactory.create(HttpError, {
error,
message: 'Error downloading csv report',
});
ErrorHandler.handleError(searchError);
} finally {
setIsExporting(false);
}
};

return (
<I18nProvider>
<>
{isDataSourceLoading && !dataSource ? (
<LoadingSpinner />
) : (
<div className='wz-search-bar hide-filter-control'>
<SearchBar
appName='th-searchbar'
{...searchBarProps}
showDatePicker={true}
showQueryInput={true}
showQueryBar={true}
showSaveQuery={true}
/>
</div>
)}
{!isDataSourceLoading && dataSource && results?.hits?.total === 0 ? (
<DiscoverNoResults />
) : null}
{!isDataSourceLoading && dataSource && results?.hits?.total > 0 ? (
jbiset marked this conversation as resolved.
Show resolved Hide resolved
<>
<SampleDataWarning />
<div className='th-dashboard-responsive'>
<DashboardByRenderer
input={{
viewMode: ViewMode.VIEW,
panels: getKPIsPanel(dataSource?.id),
isFullScreenMode: false,
filters: fetchFilters ?? [],
useMargins: true,
id: 'kpis-th-dashboard-tab',
timeRange: {
from: dateRangeFrom,
to: dateRangeTo,
},
title: 'KPIs Threat Hunting dashboard',
description: 'KPIs Dashboard of the Threat Hunting',
query: query,
refreshConfig: {
pause: false,
value: 15,
},
hidePanelTitles: true,
}}
/>
<DashboardByRenderer
input={{
viewMode: ViewMode.VIEW,
panels: getDashboardPanels(dataSource?.id, pinnedAgent),
isFullScreenMode: false,
filters: fetchFilters ?? [],
useMargins: true,
id: 'th-dashboard-tab',
timeRange: {
from: dateRangeFrom,
to: dateRangeTo,
},
title: 'Threat Hunting dashboard',
description: 'Dashboard of the Threat Hunting',
query: query,
refreshConfig: {
pause: false,
value: 15,
},
hidePanelTitles: false,
}}
/>
<EuiDataGrid
{...dataGridProps}
className={sideNavDocked ? 'dataGridDockedNav' : ''}
toolbarVisibility={{
additionalControls: (
<>
<HitsCounter
hits={results?.hits?.total}
showResetButton={false}
onResetQuery={() => {}}
tooltip={
results?.hits?.total &&
results?.hits?.total > MAX_ENTRIES_PER_QUERY
? {
ariaLabel: 'Warning',
content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas(
MAX_ENTRIES_PER_QUERY,
)} hits.`,
iconType: 'alert',
position: 'top',
}
: undefined
}
/>
<EuiButtonEmpty
disabled={
results?.hits?.total === 0 ||
!columnVisibility?.visibleColumns?.length
}
size='xs'
iconType='exportAction'
color='primary'
isLoading={isExporting}
className='euiDataGrid__controlBtn'
onClick={onClickExportResults}
>
Export Formated
</EuiButtonEmpty>
</>
),
}}
/>
{inspectedHit && (
<EuiFlyout onClose={() => setInspectedHit(undefined)} size='m'>
<EuiFlyoutHeader>
<EuiTitle>
<h2>Document details</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiFlexGroup direction='column'>
<EuiFlexItem>
<DocViewer {...docViewerProps} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutBody>
</EuiFlyout>
)}
</div>
</>
) : null}
</>
</I18nProvider>
);
};

export const DashboardThreatHunting = withErrorBoundary(DashboardTH);