diff --git a/CHANGELOG.md b/CHANGELOG.md index 45522be494..c14c8b6c41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ All notable changes to the Wazuh app project will be documented in this file. ### Changed -- Removed embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) [#6235](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6235) +- Removed embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) [#6235](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6235) [#6254](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6254) - Develop logic of a new index for the fim module [#6227](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6227) - Allow editing groups for an agent from Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250) diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx b/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx new file mode 100644 index 0000000000..3d6cdc7d5f --- /dev/null +++ b/plugins/main/public/components/common/data-grid/use-data-grid.test.tsx @@ -0,0 +1,23 @@ +import { useDataGrid, tDataGridProps } from './use-data-grid'; +import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; + +describe('useDataGrid hook', () => { + it('should return override the numbers of rows per page', () => { + + const dataGridProps: tDataGridProps = { + indexPattern: 'mocked-index-pattern', + results: {}, + defaultColumns: [], + DocViewInspectButton: () =>
, + ariaLabelledBy: '', + pagination: { + pageSize: 10, + pageSizeOptions: [10, 20, 30], + } + } + const { result } = renderHook(() => useDataGrid(dataGridProps)); + expect(result.current.pagination.pageSize).toEqual(10); + expect(result.current.pagination.pageSizeOptions).toEqual([10, 20, 30]); + }) +}); \ No newline at end of file diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts index a27cb3d2d0..a4ca8f9312 100644 --- a/plugins/main/public/components/common/data-grid/use-data-grid.ts +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -6,6 +6,7 @@ import { parseData, getFieldFormatted, parseColumns } from './data-grid-service' import { IndexPattern } from '../../../../../../src/plugins/data/common'; const MAX_ENTRIES_PER_QUERY = 10000; +const DEFAULT_PAGE_SIZE_OPTIONS = [20, 50, 100]; export type tDataGridColumn = { render?: (value: any) => string | React.ReactNode; @@ -17,11 +18,12 @@ type tDataGridProps = { defaultColumns: tDataGridColumn[]; DocViewInspectButton: ({ rowIndex }: EuiDataGridCellValueElementProps) => React.JSX.Element ariaLabelledBy: string; + pagination?: Partial; }; export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { - const { indexPattern, DocViewInspectButton, results, defaultColumns } = props; + const { indexPattern, DocViewInspectButton, results, defaultColumns, pagination: defaultPagination } = props; /** Columns **/ const [columns, setColumns] = useState(defaultColumns); const [columnVisibility, setVisibility] = useState(() => @@ -40,7 +42,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { const [sortingColumns, setSortingColumns] = useState(defaultSorting); const onSort = (sortingColumns) => { setSortingColumns(sortingColumns) }; /** Pagination **/ - const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20 }); + const [pagination, setPagination] = useState(defaultPagination || { pageIndex: 0, pageSize: 20, pageSizeOptions: DEFAULT_PAGE_SIZE_OPTIONS }); const onChangeItemsPerPage = useMemo(() => (pageSize) => setPagination((pagination) => ({ ...pagination, @@ -95,7 +97,6 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { sorting: { columns: sortingColumns, onSort }, pagination: { ...pagination, - pageSizeOptions: [20, 50, 100], onChangeItemsPerPage: onChangeItemsPerPage, onChangePage: onChangePage, } diff --git a/plugins/main/public/components/common/hooks/index.ts b/plugins/main/public/components/common/hooks/index.ts index e3ce7584c7..a4cd7dbf91 100644 --- a/plugins/main/public/components/common/hooks/index.ts +++ b/plugins/main/public/components/common/hooks/index.ts @@ -28,3 +28,4 @@ export * from './use_async_action_run_on_start'; export { useEsSearch } from './use-es-search'; export { useValueSuggestion, IValueSuggestion } from './use-value-suggestion'; export * from './use-state-storage'; +export * from './useDockedSideNav'; diff --git a/plugins/main/public/components/common/hooks/useDockedSideNav.tsx b/plugins/main/public/components/common/hooks/useDockedSideNav.tsx new file mode 100644 index 0000000000..489536b4d6 --- /dev/null +++ b/plugins/main/public/components/common/hooks/useDockedSideNav.tsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { getChrome } from '../../../kibana-services'; + +export const useDockedSideNav = () => { + const [sideNavDocked, setSideNavDocked] = useState(false); + + useEffect(() => { + const isNavDrawerSubscription = getChrome() + .getIsNavDrawerLocked$() + .subscribe((value: boolean) => { + setSideNavDocked(value); + }); + + return () => { + isNavDrawerSubscription.unsubscribe(); + }; + }, []); + + return sideNavDocked; +}; diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.js index 8c3fd06459..3d18631800 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.js +++ b/plugins/main/public/components/common/modules/modules-defaults.js @@ -19,10 +19,13 @@ import ButtonModuleExploreAgent from '../../../controllers/overview/components/o import { ButtonModuleGenerateReport } from '../modules/buttons'; import { OfficePanel } from '../../overview/office-panel'; import { GitHubPanel } from '../../overview/github-panel'; -import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities'; +import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities' import { withModuleNotForAgent } from '../hocs'; +import WazuhDiscover from '../wazuh-discover/wz-discover'; +import { threatHuntingColumns } from '../wazuh-discover/config/data-grid-columns'; import { DashboardFim } from '../../overview/fim/dashboard/dashboard'; import { InventoryFim } from '../../overview/fim/inventory/inventory'; +import React from 'react'; const DashboardTab = { id: 'dashboard', @@ -30,12 +33,26 @@ const DashboardTab = { buttons: [ButtonModuleExploreAgent, ButtonModuleGenerateReport], component: Dashboard, }; +const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*'; +const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN; + +const renderDiscoverTab = (indexName = DEFAULT_INDEX_PATTERN, columns) => { + return { + id: 'events', + name: 'Events', + buttons: [ButtonModuleExploreAgent], + component: () => + , + } +}; + const EventsTab = { - id: 'events', - name: 'Events', - buttons: [ButtonModuleExploreAgent], - component: Events, + id: 'events', + name: 'Events', + buttons: [ButtonModuleExploreAgent], + component: Events, }; + const RegulatoryComplianceTabs = [ DashboardTab, { @@ -49,8 +66,8 @@ const RegulatoryComplianceTabs = [ export const ModulesDefaults = { general: { - init: 'dashboard', - tabs: [DashboardTab, EventsTab], + init: 'events', + tabs: [DashboardTab,renderDiscoverTab(DEFAULT_INDEX_PATTERN, threatHuntingColumns)], availableFor: ['manager', 'agent'], }, fim: { diff --git a/plugins/main/public/components/common/search-bar/search-bar-service.ts b/plugins/main/public/components/common/search-bar/search-bar-service.ts index d7c546ccff..f7457faf4f 100644 --- a/plugins/main/public/components/common/search-bar/search-bar-service.ts +++ b/plugins/main/public/components/common/search-bar/search-bar-service.ts @@ -17,10 +17,14 @@ export interface SearchParams { direction: 'asc' | 'desc'; }[]; }; + dateRange?: { + from: string; + to: string; + }; } export const search = async (params: SearchParams): Promise => { - const { indexPattern, filters = [], query, pagination, sorting, fields } = params; + const { indexPattern, filters: defaultFilters = [], query, pagination, sorting, fields } = params; if(!indexPattern){ return; } @@ -31,6 +35,24 @@ export const search = async (params: SearchParams): Promise { return { @@ -128,3 +129,24 @@ export const getDiscoverPanels = ( } }; } + +export const histogramChartInput = (indexPatternName: string, filters, query, dateRangeFrom, dateRangeTo) => ({ + viewMode: ViewMode.VIEW, + panels: getDiscoverPanels(indexPatternName), + isFullScreenMode: false, + filters: filters ?? [], + useMargins: false, + id: 'wz-discover-events-histogram', + timeRange: { + from: dateRangeFrom, + to: dateRangeTo, + }, + title: 'Discover Events Histogram', + description: 'Histogram of events by date', + query: query, + refreshConfig: { + pause: false, + value: 15, + }, + hidePanelTitles: true, + }) \ No newline at end of file diff --git a/plugins/main/public/components/common/wazuh-discover/discover.scss b/plugins/main/public/components/common/wazuh-discover/discover.scss new file mode 100644 index 0000000000..db82845c2a --- /dev/null +++ b/plugins/main/public/components/common/wazuh-discover/discover.scss @@ -0,0 +1,28 @@ +.discoverContainer { + height: calc(100vh - 104px); + + .euiDataGrid--fullScreen { + height: calc(100vh - 49px); + bottom: 0; + top: auto; + } + + .discoverDataGrid { + height: calc(100vh - 496px); + } + + .discoverChartContainer { + min-height: 234px; + .dshLayout-isMaximizedPanel { + top: 0; + left: 0; + min-height: calc(100vh - 49px); + position: fixed; + z-index: 9999; + } + } +} + +.headerIsExpanded .discoverContainer { + height: calc(100vh - 153px); +} diff --git a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx index b33ac2ed87..b22c58d0d4 100644 --- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -16,7 +16,7 @@ import { EuiPanel, } from '@elastic/eui'; import { IntlProvider } from 'react-intl'; -import { IndexPattern } from '../../../../../../src/plugins/data/common'; +import { Filter, IndexPattern } from '../../../../../../src/plugins/data/common'; import { SearchResponse } from '../../../../../../src/core/server'; import { useDocViewer } from '../doc-viewer'; import DocViewer from '../doc-viewer/doc-viewer'; @@ -24,21 +24,15 @@ import { DiscoverNoResults } from '../../overview/vulnerabilities/common/compone import { LoadingSpinner } from '../../overview/vulnerabilities/common/components/loading_spinner'; import { useDataGrid, tDataGridColumn, exportSearchToCSV } from '../data-grid'; import { ErrorHandler, ErrorFactory, HttpError } from '../../../react-services/error-management'; -import { withErrorBoundary } from '../hocs'; import { HitsCounter } from '../../../kibana-integrations/discover/application/components/hits_counter'; import { formatNumWithCommas } from '../../../kibana-integrations/discover/application/helpers'; import useSearchBar from '../search-bar/use-search-bar'; import { search } from '../search-bar'; import { getPlugins } from '../../../kibana-services'; -import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; -import { getDiscoverPanels } from './config/chart'; +import { histogramChartInput } from './config/histogram-chart'; +import { useDockedSideNav } from '../hooks/useDockedSideNav' const DashboardByRenderer = getPlugins().dashboard.DashboardContainerByValueRenderer; - -/** - * ToDo: - * - add possibility to customize column render - * - add save query feature - */ +import './discover.scss'; export const MAX_ENTRIES_PER_QUERY = 10000; @@ -49,16 +43,14 @@ type WazuhDiscoverProps = { const WazuhDiscover = (props: WazuhDiscoverProps) => { const { indexPatternName, tableColumns: defaultTableColumns } = props - const { searchBarProps } = useSearchBar({ - defaultIndexPatternID: indexPatternName, - }) - const { isLoading, filters, query, indexPatterns } = searchBarProps; + const [sidebarDocked, setSidebarDocked] = useState(false); const SearchBar = getPlugins().data.ui.SearchBar; const [results, setResults] = useState({} as SearchResponse); const [inspectedHit, setInspectedHit] = useState(undefined); const [indexPattern, setIndexPattern] = useState(undefined); const [isSearching, setIsSearching] = useState(false); const [isExporting, setIsExporting] = useState(false); + const sideNavDocked = useDockedSideNav(); const onClickInspectDoc = useMemo(() => (index: number) => { const rowClicked = results.hits.hits[index]; @@ -78,12 +70,22 @@ const WazuhDiscover = (props: WazuhDiscoverProps) => { ); }; + const { searchBarProps } = useSearchBar({ + defaultIndexPatternID: indexPatternName, + }) + const { isLoading, filters, query, indexPatterns, dateRangeFrom, dateRangeTo } = searchBarProps; + const dataGridProps = useDataGrid({ ariaLabelledBy: 'Discover events table', defaultColumns: defaultTableColumns, results, indexPattern: indexPattern as IndexPattern, - DocViewInspectButton + DocViewInspectButton, + pagination: { + pageIndex: 0, + pageSize: 15, + pageSizeOptions: [15, 25, 50, 100], + } }) const { pagination, sorting, columnVisibility } = dataGridProps; @@ -95,23 +97,31 @@ const WazuhDiscover = (props: WazuhDiscoverProps) => { useEffect(() => { if (!isLoading) { + setIsSearching(true); setIndexPattern(indexPatterns?.[0] as IndexPattern); search({ indexPattern: indexPatterns?.[0] as IndexPattern, filters, query, pagination, - sorting + sorting, + dateRange: { + from: dateRangeFrom, + to: dateRangeTo, + } }).then((results) => { setResults(results); setIsSearching(false); }).catch((error) => { - const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching vulnerabilities' }) + const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching data' }) ErrorHandler.handleError(searchError); setIsSearching(false); }) } - }, [JSON.stringify(searchBarProps), JSON.stringify(pagination), JSON.stringify(sorting)]); + }, + [JSON.stringify(searchBarProps), + JSON.stringify(pagination), + JSON.stringify(sorting)]); const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; @@ -141,7 +151,7 @@ const WazuhDiscover = (props: WazuhDiscoverProps) => { return ( { } - {isSearching ? - : null} - {!isLoading && !isSearching && results?.hits?.total === 0 ? + {!isLoading && results?.hits?.total === 0 ? : null} - {!isLoading && !isSearching && results?.hits?.total > 0 ? ( + {!isLoading && results?.hits?.total > 0 ? ( <> - + - - { }} - 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} - /> - - Export Formated - - - ) - }} - /> - ) : null} +
+ + { }} + 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} + /> + + Export Formated + + + ) + }} + /> +
+ + ) : null} {inspectedHit && ( setInspectedHit(undefined)} size="m"> @@ -240,6 +234,6 @@ const WazuhDiscover = (props: WazuhDiscoverProps) => {
); -} +}; export default WazuhDiscover; \ No newline at end of file diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx index b92b1d9ae9..f4abe37010 100644 --- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx @@ -31,7 +31,7 @@ import { LoadingSpinner } from '../../common/components/loading_spinner'; // common components/hooks import DocViewer from '../../../../common/doc-viewer/doc-viewer'; import useSearchBar from '../../../../common/search-bar/use-search-bar'; -import { useAppConfig } from '../../../../common/hooks'; +import { useAppConfig, useDockedSideNav } from '../../../../common/hooks'; import { useDataGrid } from '../../../../common/data-grid/use-data-grid'; import { useDocViewer } from '../../../../common/doc-viewer/use-doc-viewer'; import { withErrorBoundary } from '../../../../common/hocs'; @@ -56,6 +56,7 @@ const InventoryVulsComponent = () => { ); const [isSearching, setIsSearching] = useState(false); const [isExporting, setIsExporting] = useState(false); + const sideNavDocked = useDockedSideNav(); const onClickInspectDoc = useMemo( () => (index: number) => { @@ -198,6 +199,7 @@ const InventoryVulsComponent = () => { isSuccess && results?.hits?.total > 0 ? (