diff --git a/CHANGELOG.md b/CHANGELOG.md index c30f696e0d..7cc0bb77e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,12 @@ All notable changes to the Wazuh app project will be documented in this file. - Add ability to disable the edition of configuration through API endpoints and UI [#6557](https://github.com/wazuh/wazuh-dashboard-plugins/issues/6557) - Added journald log collector tab [#6572](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6572) - Added HAProxy helper settings to cluster configuration [#6653](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6653) +- Added ability to open the report file or Reporting application from the toast message [#6558](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6558) +- Added support for agents to Office 365 [#6558](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6558) ### Changed -- 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) [#6285](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6285) [#6288](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6288) [#6290](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6290) [#6289](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6289) [#6286](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6286) [#6275](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6275) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6297](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6297) [#6291](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6459](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6459) [#6434](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6434) [#6504](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6504) [#6649](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6649) [#6506](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6506) [#6537](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6537) [#6528](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6528) [#6675](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6675) [#6674](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6674) +- 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) [#6285](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6285) [#6288](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6288) [#6290](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6290) [#6289](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6289) [#6286](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6286) [#6275](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6275) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6297](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6297) [#6291](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6459](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6459) [#6434](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6434) [#6504](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6504) [#6649](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6649) [#6506](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6506) [#6537](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6537) [#6528](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6528) [#6675](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6675) [#6674](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6674) [#6558](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6558) - 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) - Change how the configuration is managed in the backend side [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337) [#6519](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6519) [#6573](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6573) @@ -43,6 +45,7 @@ All notable changes to the Wazuh app project will be documented in this file. ### Fixed - Fixed the scripted fields disappear when the fields of the events index pattern was refreshed [#6237](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6237) +- Fixed an error of malformed table row on the generation of PDF reports [#6558](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6558) - Fixed the sample alerts scripts to generate valid IP ranges and file hashes [#6667](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6667) ### Removed diff --git a/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx b/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx index b7bb9c6a34..862c4e2cbe 100644 --- a/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx +++ b/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx @@ -10,7 +10,7 @@ const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); export const NetworkInterfacesTable = ({ agent }) => { return ( - + (a.label > b.label ? 1 : -1); export const NetworkPortsTable = withSOPlatformGuard( ({ agent, soPlatform }) => { return ( - + (a.label > b.label ? 1 : -1); export const NetworkSettingsTable = ({ agent }) => { return ( - + (a.label > b.label ? 1 : -1); export const PackagesTable = withSOPlatformGuard(({ agent, soPlatform }) => { return ( - + (a.label > b.label ? 1 : -1); export const ProcessesTable = withSOPlatformGuard(({ agent, soPlatform }) => { return ( - + + diff --git a/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx index 585726b3a8..65e3937cca 100644 --- a/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx +++ b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx @@ -10,7 +10,7 @@ const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); export const WindowsUpdatesTable = ({ agent }) => { return ( - + +
{agent && agent.status === API_NAME_AGENT_STATUS.DISCONNECTED && ( - + + + + + )} - + - + - + - + {agent && agent.os && agent.os.platform === 'windows' && ( - + )} diff --git a/plugins/main/public/components/common/hooks/use-reporting-communicate-search-context.ts b/plugins/main/public/components/common/hooks/use-reporting-communicate-search-context.ts new file mode 100644 index 0000000000..0129672e59 --- /dev/null +++ b/plugins/main/public/components/common/hooks/use-reporting-communicate-search-context.ts @@ -0,0 +1,23 @@ +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { updateReportingCommunicateSearchContext } from '../../../redux/actions/reportingActions'; +import { IIndexPattern } from '../../../../../../src/plugins/data/public'; +/** + * WORKAROUND: this hook stores the search context to be used by the Generate report button of + * module dashboards + * @param context + */ +export function useReportingCommunicateSearchContext(context: { + isSearching: boolean; + totalResults: number; + indexPattern: IIndexPattern; + filters: any; + time: any; + query: any; +}) { + const dispatch = useDispatch(); + useEffect(() => { + dispatch(updateReportingCommunicateSearchContext(context)); + return () => dispatch(updateReportingCommunicateSearchContext(null)); + }, [JSON.stringify(context)]); +} diff --git a/plugins/main/public/components/common/modules/buttons/generate_report.tsx b/plugins/main/public/components/common/modules/buttons/generate_report.tsx index 155963d8cb..311658588f 100644 --- a/plugins/main/public/components/common/modules/buttons/generate_report.tsx +++ b/plugins/main/public/components/common/modules/buttons/generate_report.tsx @@ -16,48 +16,65 @@ import { getUiSettings } from '../../../../kibana-services'; import { ReportingService } from '../../../../react-services'; import $ from 'jquery'; import { WzButton } from '../../../common/buttons'; +import { connect } from 'react-redux'; +const mapStateToProps = state => ({ + dataSourceSearchContext: state.reportingReducers.dataSourceSearchContext, +}); -export const ButtonModuleGenerateReport = ({agent, moduleID, disabledReport}) => { - const action = useAsyncAction(async () => { - const reportingService = new ReportingService(); - const isDarkModeTheme = getUiSettings().get('theme:darkMode'); - if (isDarkModeTheme) { +export const ButtonModuleGenerateReport = connect(mapStateToProps)( + ({ agent, moduleID, dataSourceSearchContext }) => { + const disabledReport = ![ + !dataSourceSearchContext?.isSearching, + dataSourceSearchContext?.totalResults, + dataSourceSearchContext?.indexPattern, + ].every(Boolean); + const totalResults = dataSourceSearchContext?.totalResults; + const action = useAsyncAction(async () => { + const reportingService = new ReportingService(); + const isDarkModeTheme = getUiSettings().get('theme:darkMode'); + if (isDarkModeTheme) { + //Patch to fix white text in dark-mode pdf reports + const defaultTextColor = '#DFE5EF'; - //Patch to fix white text in dark-mode pdf reports - const defaultTextColor = '#DFE5EF'; + //Patch to fix dark backgrounds in visualizations dark-mode pdf reports + const $labels = $('.euiButtonEmpty__text, .echLegendItem'); + const $vizBackground = $('.echChartBackground'); + const defaultVizBackground = $vizBackground.css('background-color'); - //Patch to fix dark backgrounds in visualizations dark-mode pdf reports - const $labels = $('.euiButtonEmpty__text, .echLegendItem'); - const $vizBackground = $('.echChartBackground'); - const defaultVizBackground = $vizBackground.css('background-color'); - - try { - $labels.css('color', 'black'); - $vizBackground.css('background-color', 'transparent'); - await reportingService.startVis2Png(moduleID, agent?.id || false) - $vizBackground.css('background-color', defaultVizBackground); - $labels.css('color', defaultTextColor); - } catch (e) { - $labels.css('color', defaultTextColor); - $vizBackground.css('background-color', defaultVizBackground); + try { + $labels.css('color', 'black'); + $vizBackground.css('background-color', 'transparent'); + await reportingService.startVis2Png(moduleID, agent?.id || false); + $vizBackground.css('background-color', defaultVizBackground); + $labels.css('color', defaultTextColor); + } catch (e) { + $labels.css('color', defaultTextColor); + $vizBackground.css('background-color', defaultVizBackground); + } + } else { + await reportingService.startVis2Png(moduleID, agent?.id || false); } - } else { - await reportingService.startVis2Png(moduleID, agent?.id || false) - } - }, [agent]); - - return ( - - Generate report - - ) -} + }, [agent]); + return ( + + Generate report + + ); + }, +); diff --git a/plugins/main/public/components/common/modules/main-agent.tsx b/plugins/main/public/components/common/modules/main-agent.tsx index 028d6933d9..3d27e26405 100644 --- a/plugins/main/public/components/common/modules/main-agent.tsx +++ b/plugins/main/public/components/common/modules/main-agent.tsx @@ -30,6 +30,14 @@ import { getAngularModule, getCore } from '../../../kibana-services'; import { compose } from 'redux'; import { withGlobalBreadcrumb } from '../hocs'; import { endpointSummary } from '../../../utils/applications'; +import { + AlertsDataSource, + AlertsDataSourceRepository, + PatternDataSource, + tParsedIndexPattern, + useDataSource, +} from '../data-source'; +import { useAsyncAction } from '../hooks'; export class MainModuleAgent extends Component { props!: { @@ -61,43 +69,6 @@ export class MainModuleAgent extends Component { this.router = $injector.get('$route'); } - async startReport() { - this.setState({ loadingReport: true }); - const syscollectorFilters: any[] = []; - const agent = - ( - this.props.agent || - store.getState().appStateReducers.currentAgentData || - {} - ).id || false; - if (this.props.section === 'syscollector' && agent) { - syscollectorFilters.push(this.filterHandler.managerQuery(agent, true)); - syscollectorFilters.push(this.filterHandler.agentQuery(agent)); - } - await this.reportingService.startVis2Png( - this.props.section, - agent, - syscollectorFilters.length ? syscollectorFilters : null, - ); - this.setState({ loadingReport: false }); - } - - renderReportButton() { - return ( - this.props.section === 'syscollector' && ( - - this.startReport()} - > - Generate report - - - ) - ); - } - renderTitle() { return ( @@ -123,7 +94,14 @@ export class MainModuleAgent extends Component { - {this.renderReportButton()} + {this.props.section === 'syscollector' && ( + + + + )} @@ -253,3 +231,41 @@ export default compose( } }), )(MainModuleAgent); + +const GenerateSyscollectorReportButton = ({ agent }) => { + const { + dataSource, + fetchFilters, + isLoading: isDataSourceLoading, + } = useDataSource({ + repository: new AlertsDataSourceRepository(), // this makes only works with alerts index pattern + DataSource: AlertsDataSource, + }); + + const action = useAsyncAction(async () => { + const reportingService = new ReportingService(); + const agentID = + (agent || store.getState().appStateReducers.currentAgentData || {}).id || + false; + await reportingService.startVis2Png('syscollector', agentID, { + indexPattern: dataSource.indexPattern, + query: { query: '', language: 'kuery' }, + filters: fetchFilters, + time: { + from: 'now-1d/d', + to: 'now', + }, + }); + }, [dataSource]); + + return ( + + Generate report + + ); +}; diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index 3bdccccf2d..18f4145861 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.tsx +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -57,7 +57,6 @@ import { DashboardFIM } from '../../overview/fim/dashboard/dashboard'; import { DashboardNIST80053 } from '../../overview/nist/dashboards/dashboard'; import { DashboardHIPAA } from '../../overview/hipaa/dashboards/dashboard'; import { DashboardTSC } from '../../overview/tsc/dashboards/dashboard'; -import { PCIDSSDataSource } from '../data-source/pattern/alerts/pci-dss/pci-dss-data-source'; import { DockerDataSource, AlertsDataSource, @@ -74,6 +73,7 @@ import { GDPRDataSource, ConfigurationAssessmentDataSource, HIPAADataSource, + PCIDSSDataSource, Office365DataSource, } from '../data-source'; import { ButtonExploreAgent } from '../../wz-agent-selector/button-explore-agent'; @@ -149,7 +149,7 @@ export const ModulesDefaults = { id: 'dashboard', name: 'Dashboard', component: DashboardGoogleCloud, - buttons: [ButtonExploreAgent], + buttons: [ButtonExploreAgent, ButtonModuleGenerateReport], }, renderDiscoverTab({ tableColumns: googleCloudColumns, @@ -205,28 +205,20 @@ export const ModulesDefaults = { id: 'dashboard', name: 'Dashboard', component: DashboardOffice365, - /* For ButtonExploreAgent to insert correctly according to the module's index pattern, the moduleIndexPatternTitle parameter is added. By default it applies the index patternt wazuh-alerts-* */ - buttons: [ - ({ ...props }) => ( - - ), - ], + buttons: [ButtonExploreAgent, ButtonModuleGenerateReport], }, { id: 'inventory', name: 'Panel', buttons: [ButtonExploreAgent], - component: withModuleNotForAgent(OfficePanel), + component: OfficePanel, }, renderDiscoverTab({ tableColumns: office365Columns, DataSource: Office365DataSource, }), ], - availableFor: ['manager'], + availableFor: ['manager', 'agent'], }, github: { init: 'dashboard', 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 7defd4228d..d3a3e6543f 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 @@ -34,7 +34,7 @@ function parseQueryString() { /** * Get the forceNow query parameter */ -function getForceNow() { +export function getForceNow() { const forceNow = parseQueryString().forceNow as string; if (!forceNow) { return; @@ -90,7 +90,7 @@ export const search = async ( gte: dateMath.parse(from).toISOString(), /* roundUp: true is used to transform the osd dateform to a generic date format For instance: the "This week" date range in the date picker. - To: now/w + To: now/w From: now/w Without the roundUp the to and from date will be the same and the search will return no results or error diff --git a/plugins/main/public/components/endpoints-summary/agent/index.tsx b/plugins/main/public/components/endpoints-summary/agent/index.tsx index 166b331e3b..07e72623a4 100644 --- a/plugins/main/public/components/endpoints-summary/agent/index.tsx +++ b/plugins/main/public/components/endpoints-summary/agent/index.tsx @@ -14,6 +14,7 @@ import { endpointSummary } from '../../../utils/applications'; import { withErrorBoundary, withReduxProvider } from '../../common/hocs'; import { compose } from 'redux'; import { PinnedAgentManager } from '../../wz-agent-selector/wz-agent-selector-service'; +import { MainModuleAgent } from '../../common/modules/main-agent'; export const AgentView = compose( withErrorBoundary, @@ -66,15 +67,30 @@ export const AgentView = compose( } if (tab === 'syscollector' && agent) { - return ; + return ( + <> + + + + ); } if (tab === 'stats' && agent) { - return ; + return ( + <> + + + + ); } if (tab === 'configuration' && agent) { - return ; + return ( + <> + + + + ); } return ( diff --git a/plugins/main/public/components/overview/amazon-web-services/dashboards/dashboard.tsx b/plugins/main/public/components/overview/amazon-web-services/dashboards/dashboard.tsx index ded7ae26bb..835dba480d 100644 --- a/plugins/main/public/components/overview/amazon-web-services/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/amazon-web-services/dashboards/dashboard.tsx @@ -24,6 +24,7 @@ import { } from '../../../common/data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -54,6 +55,18 @@ const DashboardAWSComponents: React.FC = ({}) => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/overview/docker/dashboards/dashboard.tsx b/plugins/main/public/components/overview/docker/dashboards/dashboard.tsx index add78fab8e..d923a6bf9e 100644 --- a/plugins/main/public/components/overview/docker/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/docker/dashboards/dashboard.tsx @@ -24,6 +24,7 @@ import { } from '../../../common/data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -54,6 +55,18 @@ const DashboardDockerComponent: React.FC = ({ }) => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx b/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx index e2ad74d94e..6a8b4a760d 100644 --- a/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx @@ -24,6 +24,7 @@ import { } from '../../../common/data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -54,6 +55,18 @@ const DashboardFIMComponent: React.FC = ({}) => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/overview/gdpr/dashboards/dashboard.tsx b/plugins/main/public/components/overview/gdpr/dashboards/dashboard.tsx index e1deaa203e..7e14a64a63 100644 --- a/plugins/main/public/components/overview/gdpr/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/gdpr/dashboards/dashboard.tsx @@ -24,6 +24,7 @@ import { tParsedIndexPattern, useDataSource, } from '../../../common/data-source'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -53,6 +54,18 @@ const DashboardGDPRComponent: React.FC = () => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; @@ -88,16 +101,16 @@ const DashboardGDPRComponent: React.FC = () => { {isDataSourceLoading && !dataSource ? ( ) : ( -
- -
- )} +
+ +
+ )} {dataSource && results?.hits?.total === 0 ? ( ) : null} diff --git a/plugins/main/public/components/overview/github/dashboards/dashboard.tsx b/plugins/main/public/components/overview/github/dashboards/dashboard.tsx index 89c7b6d360..84408484ae 100644 --- a/plugins/main/public/components/overview/github/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/github/dashboards/dashboard.tsx @@ -24,7 +24,7 @@ import { useDataSource, } from '../../../common/data-source'; import { GitHubDataSource } from '../../../common/data-source/pattern/alerts/github/github-data-source'; - +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -56,7 +56,19 @@ const DashboardGitHubComponent: React.FC = () => { setFilters, }); - const { query } = searchBarProps; + const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); useEffect(() => { if (isDataSourceLoading) { @@ -64,6 +76,10 @@ const DashboardGitHubComponent: React.FC = () => { } fetchData({ query, + dateRange: { + to: dateRangeTo, + from: dateRangeFrom, + }, }) .then(results => { setResults(results); @@ -75,7 +91,12 @@ const DashboardGitHubComponent: React.FC = () => { }); ErrorHandler.handleError(searchError); }); - }, [JSON.stringify(fetchFilters), JSON.stringify(query)]); + }, [ + JSON.stringify(fetchFilters), + JSON.stringify(query), + JSON.stringify(dateRangeFrom), + JSON.stringify(dateRangeTo), + ]); return ( <> @@ -84,16 +105,16 @@ const DashboardGitHubComponent: React.FC = () => { {isDataSourceLoading && !dataSource ? ( ) : ( -
- -
- )} +
+ +
+ )} {dataSource && results?.hits?.total === 0 ? ( ) : null} diff --git a/plugins/main/public/components/overview/google-cloud/dashboards/dashboard.tsx b/plugins/main/public/components/overview/google-cloud/dashboards/dashboard.tsx index e05ecabe4e..e8945b9250 100644 --- a/plugins/main/public/components/overview/google-cloud/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/google-cloud/dashboards/dashboard.tsx @@ -23,6 +23,7 @@ import { import { GoogleCloudDataSource } from '../../../common/data-source/pattern/alerts/google-cloud/google-cloud-data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -53,6 +54,18 @@ const DashboardGoogleCloudComponent: React.FC = () => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/overview/hipaa/dashboards/dashboard.tsx b/plugins/main/public/components/overview/hipaa/dashboards/dashboard.tsx index 85f1aed105..6e773cb83f 100644 --- a/plugins/main/public/components/overview/hipaa/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/hipaa/dashboards/dashboard.tsx @@ -24,6 +24,7 @@ import { useDataSource, } from '../../../common/data-source'; import { HIPAADataSource } from '../../../common/data-source/pattern/alerts/hipaa/hipaa-data-source'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -53,6 +54,18 @@ const DashboardHIPAAComponent: React.FC = () => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; @@ -89,16 +102,16 @@ const DashboardHIPAAComponent: React.FC = () => { {isDataSourceLoading && !dataSource ? ( ) : ( -
- -
- )} +
+ +
+ )} {dataSource && results?.hits?.total === 0 ? ( ) : null} diff --git a/plugins/main/public/components/overview/malware-detection/dashboard/dashboard.tsx b/plugins/main/public/components/overview/malware-detection/dashboard/dashboard.tsx index 86f217f767..edf921e7a9 100644 --- a/plugins/main/public/components/overview/malware-detection/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/malware-detection/dashboard/dashboard.tsx @@ -24,6 +24,7 @@ import { } from '../../../common/data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -54,6 +55,18 @@ const DashboardMalwareDetectionComponent: React.FC = ({ }) => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/overview/mitre/dashboard/dashboard.tsx b/plugins/main/public/components/overview/mitre/dashboard/dashboard.tsx index baa582ba19..56de38ff3a 100644 --- a/plugins/main/public/components/overview/mitre/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/mitre/dashboard/dashboard.tsx @@ -4,7 +4,6 @@ import { ViewMode } from '../../../../../../../src/plugins/embeddable/public'; import { getDashboardPanels } from './dashboard_panels'; import { I18nProvider } from '@osd/i18n/react'; import useSearchBar from '../../../common/search-bar/use-search-bar'; -import { Filter } from '../../../../../../../src/plugins/data/common'; import { SampleDataWarning } from '../../../visualize/components'; import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { @@ -23,18 +22,13 @@ import { tParsedIndexPattern, useDataSource, } from '../../../common/data-source'; - -interface DashboardThreatHuntingProps { - pinnedAgent: Filter; -} +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); const SearchBar = getPlugins().data.ui.SearchBar; const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; -export const DashboardMITRE: React.FC = ({ - pinnedAgent, -}) => { +export const DashboardMITRE: React.FC = () => { const { filters, dataSource, @@ -56,6 +50,18 @@ export const DashboardMITRE: React.FC = ({ }); const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/overview/nist/dashboards/dashboard.tsx b/plugins/main/public/components/overview/nist/dashboards/dashboard.tsx index 2be2bfcf59..301b3d1c85 100644 --- a/plugins/main/public/components/overview/nist/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/nist/dashboards/dashboard.tsx @@ -24,6 +24,7 @@ import { tParsedIndexPattern, useDataSource, } from '../../../common/data-source'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -53,6 +54,18 @@ const DashboardNIST80053Component: React.FC = () => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; @@ -88,16 +101,16 @@ const DashboardNIST80053Component: React.FC = () => { {isDataSourceLoading && !dataSource ? ( ) : ( -
- -
- )} +
+ +
+ )} {dataSource && results?.hits?.total === 0 ? ( ) : null} diff --git a/plugins/main/public/components/overview/office/dashboard/dashboard.tsx b/plugins/main/public/components/overview/office/dashboard/dashboard.tsx index 802401aa50..010586fa4c 100644 --- a/plugins/main/public/components/overview/office/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/office/dashboard/dashboard.tsx @@ -24,6 +24,7 @@ import { import { Office365DataSource } from '../../../common/data-source/pattern/alerts/office-365/office-365-data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -50,7 +51,19 @@ const DashboardOffice365Component: React.FC = () => { filters, setFilters, }); - const { query } = searchBarProps; + const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); useEffect(() => { if (isDataSourceLoading) { @@ -87,17 +100,17 @@ const DashboardOffice365Component: React.FC = () => { {isDataSourceLoading && !dataSource ? ( ) : ( -
- -
- )} +
+ +
+ )} {dataSource && results?.hits?.total === 0 ? ( ) : null} diff --git a/plugins/main/public/components/overview/pci/dashboards/dashboard.tsx b/plugins/main/public/components/overview/pci/dashboards/dashboard.tsx index 4e289ca97c..3e878a6e2c 100644 --- a/plugins/main/public/components/overview/pci/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/pci/dashboards/dashboard.tsx @@ -24,6 +24,7 @@ import { import { PCIDSSDataSource } from '../../../common/data-source/pattern/alerts/pci-dss/pci-dss-data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -54,6 +55,18 @@ const DashboardPCIDSSComponent: React.FC = () => { const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; @@ -89,16 +102,16 @@ const DashboardPCIDSSComponent: React.FC = () => { {isDataSourceLoading && !dataSource ? ( ) : ( -
- -
- )} +
+ +
+ )} {dataSource && results?.hits?.total === 0 ? ( ) : null} diff --git a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx index e812c2f832..77dbe3c24e 100644 --- a/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/threat-hunting/dashboard/dashboard.tsx @@ -51,6 +51,7 @@ import { } from '../../../common/data-source'; import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -134,6 +135,18 @@ const DashboardTH: React.FC = () => { columnVisibility.setVisibleColumns(currentColumns.map(({ id }) => id)); }, [pinnedAgent]); + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/overview/tsc/dashboards/dashboard.tsx b/plugins/main/public/components/overview/tsc/dashboards/dashboard.tsx index fb49fd59b7..071d08256d 100644 --- a/plugins/main/public/components/overview/tsc/dashboards/dashboard.tsx +++ b/plugins/main/public/components/overview/tsc/dashboards/dashboard.tsx @@ -11,19 +11,20 @@ import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner'; import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { - ErrorFactory, - ErrorHandler, - HttpError, + ErrorFactory, + ErrorHandler, + HttpError, } from '../../../../react-services/error-management'; import { compose } from 'redux'; import { SampleDataWarning } from '../../../visualize/components'; import { TSCDataSource } from '../../../common/data-source/pattern/alerts/tsc/tsc-data-souce'; import { - AlertsDataSourceRepository, - PatternDataSource, - tParsedIndexPattern, - useDataSource, + AlertsDataSourceRepository, + PatternDataSource, + tParsedIndexPattern, + useDataSource, } from '../../../common/data-source'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -32,111 +33,123 @@ const SearchBar = getPlugins().data.ui.SearchBar; const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; const DashboardTSCComponent: React.FC = () => { - const { - filters, - dataSource, - fetchFilters, - isLoading: isDataSourceLoading, - fetchData, - setFilters, - } = useDataSource({ - DataSource: TSCDataSource, - repository: new AlertsDataSourceRepository(), - }); - const [results, setResults] = useState({} as SearchResponse); + const { + filters, + dataSource, + fetchFilters, + isLoading: isDataSourceLoading, + fetchData, + setFilters, + } = useDataSource({ + DataSource: TSCDataSource, + repository: new AlertsDataSourceRepository(), + }); + const [results, setResults] = useState({} as SearchResponse); - const { searchBarProps } = useSearchBar({ - indexPattern: dataSource?.indexPattern as IndexPattern, - filters, - setFilters, - }); + const { searchBarProps } = useSearchBar({ + indexPattern: dataSource?.indexPattern as IndexPattern, + filters, + setFilters, + }); - const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + const { query, dateRangeFrom, dateRangeTo } = searchBarProps; - useEffect(() => { - if (isDataSourceLoading) { - return; - } - fetchData({ - query, - dateRange: { - from: dateRangeFrom, - to: dateRangeTo, - }, - }) - .then(results => { - setResults(results); - }) - .catch(error => { - const searchError = ErrorFactory.create(HttpError, { - error, - message: 'Error fetching alerts', - }); - ErrorHandler.handleError(searchError); - }); - }, [ - JSON.stringify(fetchFilters), - JSON.stringify(query), - JSON.stringify(dateRangeFrom), - JSON.stringify(dateRangeTo), - ]); + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); - return ( + useEffect(() => { + if (isDataSourceLoading) { + return; + } + fetchData({ + query, + dateRange: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }) + .then(results => { + setResults(results); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching alerts', + }); + ErrorHandler.handleError(searchError); + }); + }, [ + JSON.stringify(fetchFilters), + JSON.stringify(query), + JSON.stringify(dateRangeFrom), + JSON.stringify(dateRangeTo), + ]); + + return ( + <> + <> - - <> - {isDataSourceLoading && !dataSource ? ( - - ) : ( -
- -
- )} - {dataSource && results?.hits?.total === 0 ? ( - - ) : null} - {dataSource && results?.hits?.total > 0 ? ( - <> - -
- -
- - ) : null} - -
+ {isDataSourceLoading && !dataSource ? ( + + ) : ( +
+ +
+ )} + {dataSource && results?.hits?.total === 0 ? ( + + ) : null} + {dataSource && results?.hits?.total > 0 ? ( + <> + +
+ +
+ + ) : null} - ); +
+ + ); }; export const DashboardTSC = compose(withErrorBoundary)(DashboardTSCComponent); diff --git a/plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx b/plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx index 67ad78581a..512b9cb8d6 100644 --- a/plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx +++ b/plugins/main/public/components/overview/virustotal/dashboard/dashboard.tsx @@ -24,6 +24,7 @@ import { LoadingSpinner } from '../../../common/loading-spinner/loading-spinner' import { DiscoverNoResults } from '../../../common/no-results/no-results'; import { VirusTotalDataSource } from '../../../common/data-source/pattern/alerts/virustotal/virustotal-data-source'; import './virustotal_dashboard.scss'; +import { useReportingCommunicateSearchContext } from '../../../common/hooks/use-reporting-communicate-search-context'; const plugins = getPlugins(); @@ -52,6 +53,18 @@ const DashboardVT: React.FC = () => { }); const { query, dateRangeFrom, dateRangeTo } = searchBarProps; + useReportingCommunicateSearchContext({ + isSearching: isDataSourceLoading, + totalResults: results?.hits?.total ?? 0, + indexPattern: dataSource?.indexPattern, + filters: fetchFilters, + query: query, + time: { + from: dateRangeFrom, + to: dateRangeTo, + }, + }); + useEffect(() => { if (isDataSourceLoading) { return; diff --git a/plugins/main/public/components/settings/api/api-table.js b/plugins/main/public/components/settings/api/api-table.js index 3b18de2d26..edb1982251 100644 --- a/plugins/main/public/components/settings/api/api-table.js +++ b/plugins/main/public/components/settings/api/api-table.js @@ -727,7 +727,7 @@ export const ApiTable = compose( @@ -735,7 +735,7 @@ export const ApiTable = compose( { const steps = [ diff --git a/plugins/main/public/react-services/reporting.js b/plugins/main/public/react-services/reporting.js index 2306b60d80..79fcdd755d 100644 --- a/plugins/main/public/react-services/reporting.js +++ b/plugins/main/public/react-services/reporting.js @@ -10,7 +10,6 @@ * Find more information about this on the LICENSE file. */ -import $ from 'jquery'; import moment from 'moment'; import { WazuhConfig } from '../react-services/wazuh-config'; import { AppState } from './app-state'; @@ -18,10 +17,30 @@ import { WzRequest } from './wz-request'; import { Vis2PNG } from '../factories/vis2png'; import { RawVisualizations } from '../factories/raw-visualizations'; import { VisHandlers } from '../factories/vis-handlers'; -import { getAngularModule, getDataPlugin, getToasts } from '../kibana-services'; +import { + getAngularModule, + getCore, + getHttp, + getToasts, + getUiSettings, +} from '../kibana-services'; import { UI_LOGGER_LEVELS } from '../../common/constants'; import { UI_ERROR_SEVERITIES } from './error-orchestrator/types'; import { getErrorOrchestrator } from './common-services'; +import store from '../redux/store'; +import domtoimage from '../utils/dom-to-image'; +import dateMath from '@elastic/datemath'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiLink } from '@elastic/eui'; +import { reporting } from '../utils/applications'; +import { RedirectAppLinks } from '../../../../src/plugins/opensearch_dashboards_react/public'; +import { + buildOpenSearchQuery, + buildRangeFilter, + getOpenSearchQueryConfig, +} from '../../../../src/plugins/data/common'; +import { getForceNow } from '../components/common/search-bar/search-bar-service'; + const app = getAngularModule(); export class ReportingService { @@ -59,63 +78,128 @@ export class ReportingService { return idArray; } - async startVis2Png(tab, agents = false, syscollectorFilters = null) { + renderSucessReportsToast({ filename }) { + this.showToast( + 'success', + 'Report created', + <> + + + + + See the reports on + + + + + {reporting.title} + + + + + + + + window.open( + getHttp().basePath.prepend(`/reports/${filename}`), + '_blank', + ) + } + size='s' + > + Open report + + + + , + 10000, + ); + } + + async getVisualizationsFromDOM() { + const domVisualizations = document.querySelectorAll('.visualization'); + return await Promise.all( + Array.from(domVisualizations).map(async node => { + return { + element: await domtoimage.toPng(node), + width: node.clientWidth, + height: node.clientHeight, + title: node?.parentNode?.parentNode?.parentNode?.querySelector( + 'figcaption > h2 > .embPanel__titleInner', + )?.textContent, + }; + }), + ); + } + + async getDataSourceSearchContext() { + return store.getState().reportingReducers?.dataSourceSearchContext; + } + + async startVis2Png(tab, agents = false, searchContext = null) { try { - if (this.vis2png.isWorking()) { - this.showToast('danger', 'Error', 'Report in progress', 4000); - return; - } this.$rootScope.reportBusy = true; this.$rootScope.reportStatus = 'Generating report...0%'; this.$rootScope.$applyAsync(); - this.vis2png.clear(); - - const rawVisualizations = this.rawVisualizations - .getList() - .filter(this.removeTableVis); - - let idArray = []; - if (tab === 'general') { - idArray = this.removeAgentStatusVis( - rawVisualizations.map(item => item.id), - ); - } else { - idArray = rawVisualizations.map(item => item.id); - } - - const visualizationIDList = []; - for (const item of idArray) { - const tmpHTMLElement = $(`#${item}`); - if (tmpHTMLElement[0]) { - this.vis2png.assignHTMLItem(item, tmpHTMLElement); - visualizationIDList.push(item); - } - } - - const appliedFilters = await this.visHandlers.getAppliedFilters( - syscollectorFilters, - ); - const dataplugin = await getDataPlugin(); - const serverSideQuery = dataplugin.query.getOpenSearchQuery(); - const array = await this.vis2png.checkArray(visualizationIDList); + const dataSourceContext = + searchContext || (await this.getDataSourceSearchContext()); + const visualizations = await this.getVisualizationsFromDOM(); + const timeFilter = + dataSourceContext.time && dataSourceContext.indexPattern.timeFieldName + ? buildRangeFilter( + { + name: dataSourceContext.indexPattern.timeFieldName, + type: 'date', + }, + dataSourceContext.time, + dataSourceContext.indexPattern, + ) + : null; + // Build the filters to use in the server side + // Based on https://github.com/opensearch-project/OpenSearch-Dashboards/blob/2.13.0/src/plugins/data/public/query/query_service.ts#L103-L113 + const serverSideQuery = buildOpenSearchQuery( + dataSourceContext.indexPattern, + dataSourceContext.query, + [...dataSourceContext.filters, ...(timeFilter ? [timeFilter] : [])], + getOpenSearchQueryConfig(getUiSettings()), + ); const browserTimezone = moment.tz.guess(true); + /* The report for syscollector uses the keywords for from and to properties. + They are used with the format epoch_millis on the server side to get alerts data from + vulnerable packages. If these values are parsed, will cause an error due to unexpected format. + */ + const time = + tab === 'syscollector' + ? { to: dataSourceContext.time.to, from: dataSourceContext.time.from } + : { + to: dateMath.parse(dataSourceContext.time.to, { + roundUp: true, + forceNow: getForceNow(), + }), + from: dateMath.parse(dataSourceContext.time.from), + }; + const data = { - array, + array: visualizations, serverSideQuery, // Used for applying the same filters on the server side requests - filters: appliedFilters.filters, - time: appliedFilters.time, - searchBar: appliedFilters.searchBar, - tables: appliedFilters.tables, + filters: dataSourceContext.filters, + time, + searchBar: dataSourceContext?.query?.query || '', + tables: [], // TODO: check is this is used tab, section: agents ? 'agents' : 'overview', agents, browserTimezone, - indexPatternTitle: ( - await getDataPlugin().indexPatterns.get(AppState.getCurrentPattern()) - ).title, + indexPatternTitle: dataSourceContext.indexPattern.title, apiId: JSON.parse(AppState.getCurrentAPI()).id, }; @@ -123,18 +207,12 @@ export class ReportingService { tab === 'syscollector' ? `/reports/agents/${agents}/inventory` : `/reports/modules/${tab}`; - await WzRequest.genericReq('POST', apiEndpoint, data); + const response = await WzRequest.genericReq('POST', apiEndpoint, data); this.$rootScope.reportBusy = false; this.$rootScope.reportStatus = false; this.$rootScope.$applyAsync(); - this.showToast( - 'success', - 'Created report', - 'Success. Go to Dashboard management > Reporting', - 4000, - ); - return; + this.renderSucessReportsToast({ filename: response.data.filename }); } catch (error) { this.$rootScope.reportBusy = false; this.$rootScope.reportStatus = false; @@ -174,18 +252,12 @@ export class ReportingService { type === 'agentConfig' ? `/reports/agents/${obj.id}` : `/reports/groups/${obj.name}`; - await WzRequest.genericReq('POST', apiEndpoint, data); + const response = await WzRequest.genericReq('POST', apiEndpoint, data); this.$rootScope.reportBusy = false; this.$rootScope.reportStatus = false; this.$rootScope.$applyAsync(); - this.showToast( - 'success', - 'Created report', - 'Success. Go to Dashboard management > Reporting', - 4000, - ); - return; + this.renderSucessReportsToast({ filename: response.data.filename }); } catch (error) { this.$rootScope.reportBusy = false; this.$rootScope.reportStatus = false; diff --git a/plugins/main/public/redux/actions/reportingActions.js b/plugins/main/public/redux/actions/reportingActions.js index 9ada6e18e3..a6dfd76ad3 100644 --- a/plugins/main/public/redux/actions/reportingActions.js +++ b/plugins/main/public/redux/actions/reportingActions.js @@ -17,7 +17,7 @@ export const updateIsProcessing = isProcessing => { return { type: 'UPDATE_IS_PROCESSING', - isProcessing: isProcessing + isProcessing: isProcessing, }; }; @@ -28,7 +28,7 @@ export const updateIsProcessing = isProcessing => { export const updateListItemsForRemove = itemList => { return { type: 'UPDATE_LIST_ITEMS_FOR_REMOVE', - itemList: itemList + itemList: itemList, }; }; @@ -39,7 +39,7 @@ export const updateListItemsForRemove = itemList => { export const updateShowModal = showModal => { return { type: 'UPDATE_SHOW_MODAL', - showModal: showModal + showModal: showModal, }; }; @@ -49,6 +49,18 @@ export const updateShowModal = showModal => { */ export const cleanInfo = () => { return { - type: 'CLEAN_INFO' + type: 'CLEAN_INFO', }; }; + +/** + * Update reporting data source search context + * @param {Object} dataSourceSearchContext + */ +export const updateReportingCommunicateSearchContext = + dataSourceSearchContext => { + return { + type: 'UPDATE_REPORTING_DATA_SEARCH_SOURCE_CONTEXT', + dataSourceSearchContext, + }; + }; diff --git a/plugins/main/public/redux/reducers/reportingReducers.js b/plugins/main/public/redux/reducers/reportingReducers.js index 5e53baa9b2..9d5397b4c2 100644 --- a/plugins/main/public/redux/reducers/reportingReducers.js +++ b/plugins/main/public/redux/reducers/reportingReducers.js @@ -10,11 +10,14 @@ * Find more information about this on the LICENSE file. */ +import { cloneDeep } from 'lodash'; + const initialState = { isLoading: false, isProcessing: false, itemList: [], - showModal: false + showModal: false, + dataSourceSearchContext: null, }; const statusReducers = (state = initialState, action) => { @@ -22,19 +25,19 @@ const statusReducers = (state = initialState, action) => { return { ...state, isProcessing: action.isProcessing, - isLoading: action.isProcessing + isLoading: action.isProcessing, }; } if (action.type === 'UPDATE_LIST_ITEMS_FOR_REMOVE') { return { ...state, - itemList: action.itemList + itemList: action.itemList, }; } if (action.type === 'UPDATE_SHOW_MODAL') { return { ...state, - showModal: action.showModal + showModal: action.showModal, }; } if (action.type === 'CLEAN_INFO') { @@ -43,7 +46,13 @@ const statusReducers = (state = initialState, action) => { isLoading: false, isProcessing: false, itemList: [], - showModal: false + showModal: false, + }; + } + if (action.type === 'UPDATE_REPORTING_DATA_SEARCH_SOURCE_CONTEXT') { + return { + ...state, + dataSourceSearchContext: cloneDeep(action.dataSourceSearchContext), }; } diff --git a/plugins/main/public/services/index.js b/plugins/main/public/services/index.js index f74440a36d..e5de0b03af 100644 --- a/plugins/main/public/services/index.js +++ b/plugins/main/public/services/index.js @@ -14,7 +14,6 @@ import './theming'; import './routes'; import { CSVRequest } from './csv-request'; import { CommonData } from './common-data'; -import { ReportingService } from './reporting'; import { VisFactoryService } from './vis-factory-handler'; import './region-maps'; import './order-object-by'; @@ -27,6 +26,5 @@ app .service('errorHandler', ErrorHandler) .service('csvReq', CSVRequest) .service('commonData', CommonData) - .service('reportingService', ReportingService) .service('visFactoryService', VisFactoryService) .service('checkDaemonsStatus', CheckDaemonsStatus); diff --git a/plugins/main/public/services/reporting.js b/plugins/main/public/services/reporting.js deleted file mode 100644 index e44106bb66..0000000000 --- a/plugins/main/public/services/reporting.js +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Wazuh app - Reporting service - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -import $ from 'jquery'; -import moment from 'moment'; -import { WazuhConfig } from '../react-services/wazuh-config'; -import { GenericRequest } from '../react-services/generic-request'; -import { ErrorHandler } from '../react-services/error-handler'; - -export class ReportingService { - constructor( - $rootScope, - vis2png, - rawVisualizations, - visHandlers, - errorHandler, - ) { - this.$rootScope = $rootScope; - this.vis2png = vis2png; - this.rawVisualizations = rawVisualizations; - this.visHandlers = visHandlers; - this.genericReq = GenericRequest; - this.errorHandler = errorHandler; - this.wazuhConfig = new WazuhConfig(); - } - removeTableVis(visList) { - const attributes = JSON.parse(visList.attributes.visState); - return attributes.type !== 'table'; - } - - removeAgentStatusVis(idArray) { - const monitoringEnabled = - this.wazuhConfig.getConfig()['wazuh.monitoring.enabled']; - if (!monitoringEnabled) { - const visArray = idArray.filter(vis => { - return vis !== 'Wazuh-App-Overview-General-Agents-status'; - }); - return visArray; - } - return idArray; - } - - async startVis2Png(tab, isAgents = false, syscollectorFilters = null) { - try { - if (this.vis2png.isWorking()) { - ErrorHandler.handle('Report in progress', 'Reporting', { - warning: true, - }); - return; - } - this.$rootScope.reportBusy = true; - this.$rootScope.reportStatus = 'Generating report...0%'; - this.$rootScope.$applyAsync(); - - this.vis2png.clear(); - - const rawVisualizations = this.rawVisualizations - .getList() - .filter(this.removeTableVis); - - let idArray = []; - if (tab === 'general') { - idArray = this.removeAgentStatusVis( - rawVisualizations.map(item => item.id), - ); - } else { - idArray = rawVisualizations.map(item => item.id); - } - - for (const item of idArray) { - const tmpHTMLElement = $(`#${item}`); - this.vis2png.assignHTMLItem(item, tmpHTMLElement); - } - - const appliedFilters = await this.visHandlers.getAppliedFilters( - syscollectorFilters, - ); - - const array = await this.vis2png.checkArray(idArray); - const name = `wazuh-${isAgents ? 'agents' : 'overview'}-${tab}-${ - (Date.now() / 1000) | 0 - }.pdf`; - - const browserTimezone = moment.tz.guess(true); - - const data = { - array, - name, - title: isAgents ? `Agents ${tab}` : `Overview ${tab}`, - filters: appliedFilters.filters, - time: appliedFilters.time, - searchBar: appliedFilters.searchBar, - tables: appliedFilters.tables, - tab, - section: isAgents ? 'agents' : 'overview', - isAgents, - browserTimezone, - }; - - await this.genericReq.request('POST', '/reports', data); - - this.$rootScope.reportBusy = false; - this.$rootScope.reportStatus = false; - this.$rootScope.$applyAsync(); - ErrorHandler.info( - 'Success. Go to Dashboard management > Reporting', - 'Reporting', - ); - - return; - } catch (error) { - this.$rootScope.reportBusy = false; - this.$rootScope.reportStatus = false; - throw error; - } - } - - async startConfigReport(obj, type, components) { - try { - this.$rootScope.reportBusy = true; - this.$rootScope.reportStatus = 'Generating PDF document...'; - this.$rootScope.$applyAsync(); - - const docType = - type === 'agentConfig' - ? `wazuh-agent-${obj.id}` - : `wazuh-group-${obj.name}`; - - const name = `${docType}-configuration-${(Date.now() / 1000) | 0}.pdf`; - const browserTimezone = moment.tz.guess(true); - - const data = { - array: [], - name, - filters: [ - type === 'agentConfig' ? { agent: obj.id } : { group: obj.name }, - ], - time: '', - searchBar: '', - tables: [], - tab: type, - browserTimezone, - components, - }; - - await this.genericReq.request('POST', '/reports', data); - - this.$rootScope.reportBusy = false; - this.$rootScope.reportStatus = false; - this.$rootScope.$applyAsync(); - ErrorHandler.info( - 'Success. Go to Dashboard management > Reporting', - 'Reporting', - ); - - return; - } catch (error) { - this.$rootScope.reportBusy = false; - this.$rootScope.reportStatus = false; - this.$rootScope.$applyAsync(); - throw error; - } - } -} diff --git a/plugins/main/public/utils/dom-to-image.js b/plugins/main/public/utils/dom-to-image.js index 9e07aa916b..ec88dc9590 100644 --- a/plugins/main/public/utils/dom-to-image.js +++ b/plugins/main/public/utils/dom-to-image.js @@ -477,7 +477,9 @@ resolve(image); }; image.onerror = reject; - image.src = uri; + // Wazuh: fix an error creating image from map visualizations + // https://github.com/tsayen/dom-to-image/issues/243#issuecomment-414955354 + image.src = encodeURI(uri); }); } diff --git a/plugins/main/server/controllers/wazuh-reporting.ts b/plugins/main/server/controllers/wazuh-reporting.ts index 3b43daa7cc..e6362fbec2 100644 --- a/plugins/main/server/controllers/wazuh-reporting.ts +++ b/plugins/main/server/controllers/wazuh-reporting.ts @@ -393,6 +393,7 @@ export class WazuhReportingCtrl { body: { success: true, message: `Report ${context.wazuhEndpointParams.filename} was created`, + filename: context.wazuhEndpointParams.filename, }, }); } catch (error) { @@ -687,6 +688,7 @@ export class WazuhReportingCtrl { body: { success: true, message: `Report ${context.wazuhEndpointParams.filename} was created`, + filename: context.wazuhEndpointParams.filename, }, }); } catch (error) { @@ -1026,6 +1028,7 @@ export class WazuhReportingCtrl { body: { success: true, message: `Report ${context.wazuhEndpointParams.filename} was created`, + filename: context.wazuhEndpointParams.filename, }, }); } catch (error) { @@ -1317,6 +1320,7 @@ export class WazuhReportingCtrl { body: { success: true, message: `Report ${context.wazuhEndpointParams.filename} was created`, + filename: context.wazuhEndpointParams.filename, }, }); } catch (error) { diff --git a/plugins/main/server/lib/reporting/printer.ts b/plugins/main/server/lib/reporting/printer.ts index 0c21e518b6..ebcae08fdd 100644 --- a/plugins/main/server/lib/reporting/printer.ts +++ b/plugins/main/server/lib/reporting/printer.ts @@ -12,6 +12,18 @@ import { REPORTS_PRIMARY_COLOR } from '../../../common/constants'; import { Logger } from 'opensearch-dashboards/server'; import { IConfigurationEnhanced } from '../../../../wazuh-core/server'; +interface IVisualization { + title: string; + element: string; + height: number; + width?: number; +} + +type IVisualizationExtended = IVisualization & { + id: string; + width: number; +}; + const COLORS = { PRIMARY: REPORTS_PRIMARY_COLOR, }; @@ -356,77 +368,99 @@ export class ReportPrinter { this.addContent({ text: '\n' }); this.logger.debug('Time range and filters rendered'); } - addVisualizations(visualizations, isAgents, tab) { - this.logger.debug(`${visualizations.length} visualizations for tab ${tab}`); - const single_vis = visualizations.filter(item => item.width >= 600); - const double_vis = visualizations.filter(item => item.width < 600); - single_vis.forEach(visualization => { - const title = this.checkTitle(visualization, isAgents, tab); - this.addContent({ - id: 'singlevis' + title[0]._source.title, - text: title[0]._source.title, + private addVisualizationSingle(visualization: IVisualizationExtended) { + this.addContent({ + id: 'singlevis' + visualization.id, + text: visualization.title, + style: 'h3', + }); + this.addContent({ + columns: [{ image: visualization.element, width: 500 }], + }); + this.addNewLine(); + } + private addVisualizationSplit( + split: [IVisualizationExtended, IVisualizationExtended], + ) { + this.addContent({ + columns: split.map(visualization => ({ + id: 'splitvis' + visualization.id, + text: visualization.title, style: 'h3', - }); - this.addContent({ - columns: [{ image: visualization.element, width: 500 }], - }); - this.addNewLine(); + width: 280, + })), }); - let pair = []; + this.addContent({ + columns: split.map(visualization => ({ + image: visualization.element, + width: 270, + })), + }); - for (const item of double_vis) { - pair.push(item); - if (pair.length === 2) { - const title_1 = this.checkTitle(pair[0], isAgents, tab); - const title_2 = this.checkTitle(pair[1], isAgents, tab); + this.addNewLine(); + } + private addVisualizationSplitSingle(visualization: IVisualizationExtended) { + this.addContent({ + columns: [ + { + id: 'splitsinglevis' + visualization.id, + text: visualization.title, + style: 'h3', + width: 280, + }, + ], + }); + this.addContent({ + columns: [{ image: visualization.element, width: 280 }], + }); + this.addNewLine(); + } + addVisualizations(visualizations: IVisualization[]) { + this.logger.debug(`Add visualizations [${visualizations.length}]`); + const sanitazedVisualizations: IVisualizationExtended[] = + visualizations.map((visualization, index) => ({ + ...visualization, + title: visualization.title || '', + id: `${visualization.title || ''}.${index}`, + })); + const { single: fullWidthVisualizations, split: splitWidthVisualizations } = + sanitazedVisualizations.reduce( + (accum, visualization) => { + ( + (visualization.width >= 600 + ? accum.single + : accum.split) as IVisualizationExtended[] + ).push(visualization); + return accum; + }, + { single: [], split: [] }, + ); - this.addContent({ - columns: [ - { - id: 'splitvis' + title_1[0]._source.title, - text: title_1[0]._source.title, - style: 'h3', - width: 280, - }, - { - id: 'splitvis' + title_2[0]._source.title, - text: title_2[0]._source.title, - style: 'h3', - width: 280, - }, - ], - }); + fullWidthVisualizations.forEach(visualization => + this.addVisualizationSingle(visualization), + ); - this.addContent({ - columns: [ - { image: pair[0].element, width: 270 }, - { image: pair[1].element, width: 270 }, - ], - }); + const splitBy = 2; + const splits = splitWidthVisualizations.reduce(function ( + accum, + value, + index, + array, + ) { + if (index % splitBy === 0) + accum.push(array.slice(index, index + splitBy)); + return accum; + }, + []); - this.addNewLine(); - pair = []; + splits.forEach(split => { + if (split.length === splitBy) { + return this.addVisualizationSplit(split); } - } - - if (double_vis.length % 2 !== 0) { - const item = double_vis[double_vis.length - 1]; - const title = this.checkTitle(item, isAgents, tab); - this.addContent({ - columns: [ - { - id: 'splitsinglevis' + title[0]._source.title, - text: title[0]._source.title, - style: 'h3', - width: 280, - }, - ], - }); - this.addContent({ columns: [{ image: item.element, width: 280 }] }); - this.addNewLine(); - } + this.addVisualizationSplitSingle(split[0]); + }); } formatDate(date: Date): string { this.logger.debug(`Format date ${date}`); diff --git a/plugins/main/server/lib/reporting/summary-table.test.ts b/plugins/main/server/lib/reporting/summary-table.test.ts new file mode 100644 index 0000000000..1629d5b234 --- /dev/null +++ b/plugins/main/server/lib/reporting/summary-table.test.ts @@ -0,0 +1,125 @@ +import SummaryTable from './summary-table'; +import overviewSummaryTableDefinitions from './summary-tables-definitions/overview/index'; + +describe('Summary table', () => { + it.skip.each` + summarySetup | columns + ${overviewSummaryTableDefinitions.general[0]} | ${['Rule ID', 'Description', 'Level', 'Count']} + `('get columns from summary setup', ({ summarySetup, columns }) => { + const summaryTable = new SummaryTable( + {}, + 'now/1h', + 'now', + [], + [], + summarySetup, + 'pattern', + ); + + expect(summaryTable._columns).toEqual(columns); + }); + + it.each` + aggregationsResponse | summarySetup | title | columns | rows + ${{ '2': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ + '3': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + key: '4507', + doc_count: 5, + }, { + '3': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ + '4': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + key: '4508 - Description', + doc_count: 6, + }], + }, + key: '4508', + doc_count: 8, + }, { + '3': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ + '4': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ + '5': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + key: 10, + doc_count: 5, + }], + }, + key: '4509 - Description', + doc_count: 21, + }], + }, + key: '4509', + doc_count: 30, + }, { + '3': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ + '4': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ + '5': { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + key: 100, + doc_count: 12, + }], + }, + key: '4510 - Description', + doc_count: 14, + }], + }, + key: '4510', + doc_count: 50, + }], + } }} | ${overviewSummaryTableDefinitions.general[0]} | ${'Alerts summary'} | ${['Rule ID', 'Description', 'Level', 'Count']} | ${[['4507', undefined, undefined, 5], ['4508', '4508 - Description', undefined, 6], ['4509', '4509 - Description', 10, 5], ['4510', '4510 - Description', 100, 12]]} + `( + 'format response to table', + ({ aggregationsResponse, summarySetup, title, columns, rows }) => { + const summaryTable = new SummaryTable( + {}, + 'now/1h', + 'now', + [], + [], + summarySetup, + 'pattern', + ); + + expect( + summaryTable._formatResponseToTable(aggregationsResponse).title, + ).toBe(title); + expect( + summaryTable._formatResponseToTable(aggregationsResponse).columns, + ).toEqual(columns); + expect( + summaryTable._formatResponseToTable(aggregationsResponse).rows, + ).toEqual(rows); + }, + ); +}); diff --git a/plugins/main/server/lib/reporting/summary-table.ts b/plugins/main/server/lib/reporting/summary-table.ts index 4bf31ab408..051f7491fe 100644 --- a/plugins/main/server/lib/reporting/summary-table.ts +++ b/plugins/main/server/lib/reporting/summary-table.ts @@ -101,7 +101,7 @@ export default class SummaryTable { this._rows = rawResponse[firstKey].buckets.reduce((totalRows, bucket) => { const nextKey = firstKey + 1; - this._buildRow(bucket, nextKey, totalRows); + this._buildRow(bucket, nextKey, totalRows, this._columns.length); return totalRows; }, []); @@ -122,17 +122,41 @@ export default class SummaryTable { bucket: any, nextAggKey: number, totalRows: any[], + totalColumns: number, row: any[] = [], ): any[] { const newRow = [...row, bucket.key]; - // If there is a next aggregation, repeat the process - if (bucket[nextAggKey.toString()]?.buckets?.length) { - bucket[nextAggKey.toString()].buckets.forEach(newBucket => { - this._buildRow(newBucket, nextAggKey + 1, totalRows, newRow); - }); - } - // Add the Count as the last item in the row - else if (bucket.doc_count) { + const indexColumn = newRow.length - 1; + + if (indexColumn < totalColumns - 1) { + // If there is a next aggregation, repeat the process + if (bucket[nextAggKey.toString()]?.buckets?.length) { + bucket[nextAggKey.toString()].buckets.forEach(newBucket => { + this._buildRow( + newBucket, + nextAggKey + 1, + totalRows, + totalColumns, + newRow, + ); + }); + } else { + // Calculate the cells to fill due to the aggregation buckets are missing + // The last column is the Count and this value is coming from the last bucket available + // totalColumns starts from 1. totalColumns - 1 gets the index of the last column + // indexColumn starts from 0 + const fillCellsLength = totalColumns - 1 - indexColumn - 1; + /* Fill the row with undefined values. + Note: The undefined values are mapped to `"-"` on the methods of the reporting. + */ + if (fillCellsLength) { + newRow.push(...Array.from({ length: fillCellsLength })); + } + newRow.push(bucket.doc_count); + totalRows.push(newRow); + } + // Add the Count as the last item in the row + } else { newRow.push(bucket.doc_count); totalRows.push(newRow); }