diff --git a/CHANGELOG.md b/CHANGELOG.md index 5591927a8b..322c9d9119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Changed ossec to wazuh in sample-data [#3121](https://github.com/wazuh/wazuh-kibana-app/pull/3121) - Changed empty fields in FIM tables and `syscheck.value_name` in discovery now show an empty tag for visual clarity [#3279](https://github.com/wazuh/wazuh-kibana-app/pull/3279) - Adapted the Mitre tactics and techniques resources to use the API endpoints [#3346](https://github.com/wazuh/wazuh-kibana-app/pull/3346) +- Refactored as module tabs and buttons are rendered [#3494](https://github.com/wazuh/wazuh-kibana-app/pull/3494) ### Fixed diff --git a/public/components/agents/fim/main.tsx b/public/components/agents/fim/main.tsx index 99c593a792..e5d5f339a3 100644 --- a/public/components/agents/fim/main.tsx +++ b/public/components/agents/fim/main.tsx @@ -4,13 +4,14 @@ import '../../common/modules/module.scss'; import { connect } from 'react-redux'; import { PromptNoActiveAgent, PromptNoSelectedAgent } from '../prompts'; import { compose } from 'redux'; -import { withGuard, withUserAuthorizationPrompt } from '../../common/hocs'; +import { withAgentSupportModule, withGuard, withUserAuthorizationPrompt } from '../../common/hocs'; const mapStateToProps = (state) => ({ currentAgentData: state.appStateReducers.currentAgentData, }); export const MainFim = compose( + withAgentSupportModule, connect(mapStateToProps), withGuard( (props) => !(props.currentAgentData && props.currentAgentData.id && props.agent), diff --git a/public/components/agents/prompts/index.ts b/public/components/agents/prompts/index.ts index 1e567a6281..5519dfa4e7 100644 --- a/public/components/agents/prompts/index.ts +++ b/public/components/agents/prompts/index.ts @@ -9,8 +9,9 @@ * * Find more information about this on the LICENSE file. */ -export { PromptAgentNoSupportModule } from './prompt-agent-no-support-module'; -export { PromptNoActiveAgent, PromptNoActiveAgentWithoutSelect } from './prompt-no-active-agent'; -export { PromptNoSelectedAgent } from './prompt-no-selected-agent'; -export { PromptSelectAgent } from './prompt-select-agent'; -export { PromptAgentFeatureVersion } from './prompt-agent-feature-version'; +export * from './prompt-agent-no-support-module'; +export * from './prompt-no-active-agent'; +export * from './prompt-no-selected-agent'; +export * from './prompt-select-agent'; +export * from './prompt-agent-feature-version'; +export * from './prompt_module_not_for_agent'; diff --git a/public/components/agents/prompts/prompt_module_not_for_agent.tsx b/public/components/agents/prompts/prompt_module_not_for_agent.tsx new file mode 100644 index 0000000000..500b230c3d --- /dev/null +++ b/public/components/agents/prompts/prompt_module_not_for_agent.tsx @@ -0,0 +1,52 @@ +/* + * Wazuh app - Prompt when an agent doesn't support some module + * Copyright (C) 2015-2021 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 React from 'react'; +import { useDispatch } from 'react-redux'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { updateCurrentAgentData } from '../../../redux/actions/appStateActions'; +import { useFilterManager } from '../../common/hooks'; + +type PromptSelectAgentProps = { + body?: string; + title: string; + agentSelectionProps: { + setAgent: (agent: boolean) => void + } +}; + +export const PromptModuleNotForAgent = ({ body, title, ...agentSelectionProps }: PromptSelectAgentProps) => { + const dispatch = useDispatch(); + const filterManager = useFilterManager(); + + const unpinAgent = async () => { + dispatch(updateCurrentAgentData({})); + await agentSelectionProps.setAgent(false); + const filters = filterManager.filters.filter(x => { + return x.meta.key !== 'agent.id'; + }); + filterManager.setFilters(filters); + }; + + return ( + {title}} + body={body &&

{body}

} + actions={ + + Unpin agent + + } + /> + ); +}; \ No newline at end of file diff --git a/public/components/agents/sca/main.tsx b/public/components/agents/sca/main.tsx index 8daf51de54..1307bfa734 100644 --- a/public/components/agents/sca/main.tsx +++ b/public/components/agents/sca/main.tsx @@ -15,13 +15,14 @@ import { Inventory } from './index'; import { connect } from 'react-redux'; import { compose } from 'redux'; import { PromptSelectAgent, PromptNoSelectedAgent } from '../prompts'; -import { withGuard, withUserAuthorizationPrompt } from '../../common/hocs'; +import { withGuard, withUserAuthorizationPrompt, withAgentSupportModule } from '../../common/hocs'; const mapStateToProps = (state) => ({ currentAgentData: state.appStateReducers.currentAgentData, }); export const MainSca = compose( + withAgentSupportModule, withUserAuthorizationPrompt([ [ {action: 'agent:read', resource: 'agent:id:*'}, diff --git a/public/components/agents/vuls/main.tsx b/public/components/agents/vuls/main.tsx index d4aa148163..07affef2de 100644 --- a/public/components/agents/vuls/main.tsx +++ b/public/components/agents/vuls/main.tsx @@ -4,13 +4,14 @@ import '../../common/modules/module.scss'; import { connect } from 'react-redux'; import { PromptNoSelectedAgent, PromptNoActiveAgent } from '../prompts'; import { compose } from 'redux'; -import { withGuard, withUserAuthorizationPrompt } from '../../common/hocs'; +import { withGuard, withUserAuthorizationPrompt, withAgentSupportModule } from '../../common/hocs'; const mapStateToProps = (state) => ({ currentAgentData: state.appStateReducers.currentAgentData, }); export const MainVuls = compose( + withAgentSupportModule, connect(mapStateToProps), withGuard( (props) => !((props.currentAgentData && props.currentAgentData.id) && props.agent), diff --git a/public/components/common/hocs/index.ts b/public/components/common/hocs/index.ts index a08b1f9c64..4c84b317e5 100644 --- a/public/components/common/hocs/index.ts +++ b/public/components/common/hocs/index.ts @@ -21,3 +21,4 @@ export * from './withButtonOpenOnClick'; export * from './withAgentSupportModule'; export * from './withUserLogged'; export * from './error-boundary/with-error-boundary'; +export * from './with_module_tab_loader'; diff --git a/public/components/common/hocs/withAgentSupportModule.tsx b/public/components/common/hocs/withAgentSupportModule.tsx index a613f39539..f5c738d63b 100644 --- a/public/components/common/hocs/withAgentSupportModule.tsx +++ b/public/components/common/hocs/withAgentSupportModule.tsx @@ -12,9 +12,17 @@ import { PromptAgentNoSupportModule } from '../../agents/prompts'; import { withGuard } from '../../common/hocs'; import { hasAgentSupportModule } from '../../../react-services/wz-agents'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; -export const withAgentSupportModule = WrappedComponent => +const mapStateToProps = (state) => ({ + agent: state.appStateReducers.currentAgentData, +}); + +export const withAgentSupportModule = WrappedComponent => compose( + connect(mapStateToProps), withGuard( - ({agent, component}) => Object.keys(agent).length && !hasAgentSupportModule(agent, component), + ({agent, moduleID}) => Object.keys(agent).length && !hasAgentSupportModule(agent, moduleID), PromptAgentNoSupportModule - )(WrappedComponent) + ) +)(WrappedComponent) diff --git a/public/components/common/hocs/with_module_not_for_agent.tsx b/public/components/common/hocs/with_module_not_for_agent.tsx new file mode 100644 index 0000000000..ca362f85a1 --- /dev/null +++ b/public/components/common/hocs/with_module_not_for_agent.tsx @@ -0,0 +1,29 @@ +/* + * Wazuh app - React HOC to show a prompt when a module is not available for agents and let to unpin the agent + * Copyright (C) 2015-2021 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 React from 'react'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { withGuard } from './withGuard'; +import { PromptModuleNotForAgent } from '../../agents/prompts'; + +const mapStateToProps = (state) => ({ + agent: state.appStateReducers.currentAgentData, +}); + +export const withModuleNotForAgent = WrappedComponent => compose( + connect(mapStateToProps), + withGuard( + ({agent}) => agent?.id, + (props) => + ) +)(WrappedComponent); diff --git a/public/components/common/hocs/with_module_tab_loader.tsx b/public/components/common/hocs/with_module_tab_loader.tsx new file mode 100644 index 0000000000..637dda21da --- /dev/null +++ b/public/components/common/hocs/with_module_tab_loader.tsx @@ -0,0 +1,42 @@ +/* + * Wazuh app - React HOC to show a loader used for Dashboard adn Events module tabs + * Copyright (C) 2015-2021 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 React, { useEffect, useState } from 'react'; +import { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import { useRootScope } from '../hooks'; + +export const withModuleTabLoader = WrappedComponent => props => { + const $rootScope = useRootScope(); + const [showTab, setShowTab] = useState(); + useEffect(() => { + if($rootScope){ + $rootScope.loadingDashboard = true; + $rootScope.$applyAsync(); + setTimeout(() => { + setShowTab(true); + }, 100); + return () => { + $rootScope.loadingDashboard = false; + $rootScope.$applyAsync(); + } + } + },[$rootScope]); + + return showTab ? : ( + <> + +
+ +
+ + ) +} \ No newline at end of file diff --git a/public/components/common/hooks/useRootScope.ts b/public/components/common/hooks/useRootScope.ts index de6d0b9628..1f36e253ea 100644 --- a/public/components/common/hooks/useRootScope.ts +++ b/public/components/common/hooks/useRootScope.ts @@ -9,14 +9,14 @@ * * Find more information about this on the LICENSE file. */ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { getAngularModule } from '../../../kibana-services'; export function useRootScope(){ - const refRootScope = useRef(); + const [refRootScope,setRefRootScope] = useState(); useEffect(() => { const app = getAngularModule(); - refRootScope.current = app.$injector.get('$rootScope'); + setRefRootScope(app.$injector.get('$rootScope')); },[]); - return refRootScope.current; + return refRootScope; }; diff --git a/public/components/common/modules/buttons/generate_report.tsx b/public/components/common/modules/buttons/generate_report.tsx new file mode 100644 index 0000000000..f56622bab6 --- /dev/null +++ b/public/components/common/modules/buttons/generate_report.tsx @@ -0,0 +1,63 @@ +/* + * Wazuh app - Component for the module generate reports + * Copyright (C) 2015-2021 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 React from 'react'; +import { useAsyncAction } from '../../hooks'; +import { getUiSettings } from '../../../../kibana-services'; +import { ReportingService } from '../../../../react-services'; +import $ from 'jquery'; +import { WzButton } from '../../../common/buttons'; + + +export const ButtonModuleGenerateReport = ({agent, moduleID, disabledReport}) => { + 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 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); + } + } else { + await reportingService.startVis2Png(moduleID, agent?.id || false) + } + }); + + return ( + + Generate report + + ) +} + diff --git a/public/components/common/modules/buttons/index.ts b/public/components/common/modules/buttons/index.ts new file mode 100644 index 0000000000..76dedb877b --- /dev/null +++ b/public/components/common/modules/buttons/index.ts @@ -0,0 +1,13 @@ +/* + * Wazuh app - Module buttons components + * Copyright (C) 2015-2021 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. + */ + +export * from './generate_report'; \ No newline at end of file diff --git a/public/components/common/modules/dashboard.tsx b/public/components/common/modules/dashboard.tsx index 4827c26e28..4f07a74b7e 100644 --- a/public/components/common/modules/dashboard.tsx +++ b/public/components/common/modules/dashboard.tsx @@ -13,8 +13,14 @@ import { Component } from 'react'; import { ModulesHelper } from './modules-helper' import { getAngularModule } from '../../../kibana-services'; +import { withAgentSupportModule, withModuleTabLoader } from '../hocs'; +import { compose } from 'redux'; +import React from 'react'; -export class Dashboard extends Component { +export const Dashboard = compose( + withAgentSupportModule, + withModuleTabLoader +)(class Dashboard extends Component { _isMount = false; constructor(props) { super(props); @@ -43,6 +49,7 @@ export class Dashboard extends Component { } render() { - return false; + return null; } -} +}) + diff --git a/public/components/common/modules/events.tsx b/public/components/common/modules/events.tsx index 55eef4103a..5e887aa082 100644 --- a/public/components/common/modules/events.tsx +++ b/public/components/common/modules/events.tsx @@ -19,11 +19,16 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiOverlayMask, EuiOutsideClickDe import { PatternHandler } from '../../../react-services/pattern-handler'; import { enhanceDiscoverEventsCell } from './events-enhance-discover-fields'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; +import { withAgentSupportModule, withModuleTabLoader } from '../hocs'; +import { compose } from 'redux'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; -export class Events extends Component { +export const Events = compose( + withAgentSupportModule, + withModuleTabLoader +)(class Events extends Component { intervalCheckExistsDiscoverTableTime: number = 200; isMount: boolean; state: { @@ -292,4 +297,4 @@ export class Events extends Component { ) } -} +}) diff --git a/public/components/common/modules/index.ts b/public/components/common/modules/index.ts index 7b19779c33..bbf2232cd7 100644 --- a/public/components/common/modules/index.ts +++ b/public/components/common/modules/index.ts @@ -10,8 +10,6 @@ * Find more information about this on the LICENSE file. */ -export { Events } from './events'; -export { Dashboard } from './dashboard'; -export { Loader } from './loader'; -export { Settings } from './settings'; -export { ModulesHelper } from './modules-helper.js'; +export * from './dashboard'; +export * from './events'; +export * from './modules-helper.js'; diff --git a/public/components/common/modules/loader.tsx b/public/components/common/modules/loader.tsx deleted file mode 100644 index 98a069ec85..0000000000 --- a/public/components/common/modules/loader.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Wazuh app - Integrity monitoring components - * Copyright (C) 2015-2021 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 React, { Component, Fragment } from 'react'; -import { getAngularModule } from '../../../kibana-services'; -import { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; - -const app = getAngularModule(); - -export class Loader extends Component { - constructor(props) { - super(props); - } - - componentDidMount() { - this.$rootScope = app.$injector.get('$rootScope'); - this.$rootScope.loadingDashboard = true; - this.$rootScope.$applyAsync(); - } - - componentWillUnmount() { - this.$rootScope.loadingDashboard = false; - this.$rootScope.$applyAsync(); - } - - redirect() { - setTimeout(() => { - this.props.loadSection(this.props.redirect); - }, 100); - } - - render() { - const redirect = this.redirect(); - return ( - - -
- -
- {redirect} -
- ); - } -} diff --git a/public/components/common/modules/main-agent.tsx b/public/components/common/modules/main-agent.tsx index 8b5eb683ba..5a02871986 100644 --- a/public/components/common/modules/main-agent.tsx +++ b/public/components/common/modules/main-agent.tsx @@ -31,18 +31,8 @@ import { FilterHandler } from '../../../utils/filter-handler'; import { AppState } from '../../../react-services/app-state'; import { ReportingService } from '../../../react-services/reporting'; import { WAZUH_MODULES } from '../../../../common/wazuh-modules'; -import { Events, Dashboard, Loader, Settings } from '../../common/modules'; -import WzReduxProvider from '../../../redux/wz-redux-provider'; import { AgentInfo } from '../../common/welcome/agents-info'; -import Overview from '../../wz-menu/wz-menu-overview'; -import { MainFim } from '../../agents/fim'; -import { MainSca } from '../../agents/sca'; -import { MainMitre } from '../modules/main-mitre'; import { getAngularModule } from '../../../kibana-services'; -import { withAgentSupportModule } from '../../../components/common/hocs'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; -import { ModuleMitreAttackIntelligence } from '../../overview/mitre_attack_intelligence'; export class MainModuleAgent extends Component { props!: { @@ -248,22 +238,11 @@ export class MainModuleAgent extends Component { ); } - renderSettingsButton() { - return ( - - this.onSelectedTabChanged('settings')}> - Configuration - - - ); - } render() { const { agent, section, selectView } = this.props; const title = this.renderTitle(); + const ModuleTabView = this.props.tabs.find(tab => tab.id === selectView); return (
@@ -287,29 +266,26 @@ export class MainModuleAgent extends Component {
{this.props.renderTabs()} - {(selectView === 'dashboard') && - this.props.renderReportButton() - } - {(this.props.buttons || []).includes('dashboard') && - this.props.renderDashboardButton() - } - {(this.props.buttons || []).includes('settings') && - this.renderSettingsButton() - } + + + {ModuleTabView && ModuleTabView.buttons && ModuleTabView.buttons.map((ModuleViewButton, index) => + typeof ModuleViewButton !== 'string' ? : null)} + +
}
- {!['syscollector', 'configuration'].includes(this.props.section) && - + {!['syscollector', 'configuration'].includes(section) && + ModuleTabView && ModuleTabView.component && } } {(!agent || !agent.os) && @@ -318,41 +294,3 @@ export class MainModuleAgent extends Component { ); } } - - - -const mapStateToProps = state => ({ - agent: state.appStateReducers.currentAgentData -}); - -const ModuleTabViewer = compose( - connect(mapStateToProps), - withAgentSupportModule -)((props) => { - const { section, selectView } = props; - return <> - {selectView === 'events' && - - } - {selectView === 'loader' && - props.loadSection(section)} - redirect={props.afterLoad}> - } - {selectView === 'dashboard' && - - } - {selectView === 'settings' && - - } - - - {/* ---------------------MODULES WITH CUSTOM PANELS--------------------------- */} - {section === 'fim' && selectView==='inventory' && } - {section === 'sca' && selectView==='inventory' && } - {section === 'mitre' && selectView === 'inventory' && } - {section === 'mitre' && selectView === 'intelligence' && } - {/* {['pci', 'gdpr', 'hipaa', 'nist', 'tsc'].includes(section) && selectView === 'inventory' && props.onSelectedTabChanged(id)} />} */} - {/* -------------------------------------------------------------------------- */} - -}) \ No newline at end of file diff --git a/public/components/common/modules/main-mitre.tsx b/public/components/common/modules/main-mitre.tsx index 2cbaa7e2f5..1d8584658d 100644 --- a/public/components/common/modules/main-mitre.tsx +++ b/public/components/common/modules/main-mitre.tsx @@ -12,10 +12,11 @@ import React, { Component } from 'react'; import { Mitre } from '../../../components/overview/mitre/mitre'; -import { withUserAuthorizationPrompt } from '../hocs'; +import { withUserAuthorizationPrompt, withAgentSupportModule } from '../hocs'; import { compose } from 'redux'; export const MainMitre = compose( + withAgentSupportModule, withUserAuthorizationPrompt([ { action: 'mitre:read', resource: '*:*:*' }, ]) diff --git a/public/components/common/modules/main-overview.tsx b/public/components/common/modules/main-overview.tsx index 8cf7c4bf9b..a50955fb5b 100644 --- a/public/components/common/modules/main-overview.tsx +++ b/public/components/common/modules/main-overview.tsx @@ -26,27 +26,20 @@ import { } from '@elastic/eui'; import '../../common/modules/module.scss'; import { updateGlobalBreadcrumb } from '../../../redux/actions/globalBreadcrumbActions'; + import store from '../../../redux/store'; import { ReportingService } from '../../../react-services/reporting'; import { AppNavigate } from '../../../react-services/app-navigate'; import { WAZUH_MODULES } from '../../../../common/wazuh-modules'; -import { Events, Dashboard, Loader, Settings } from '../../common/modules'; -import OverviewActions from '../../../controllers/overview/components/overview-actions/overview-actions'; -import { MainFim } from '../../agents/fim'; - -import { MainVuls } from '../../agents/vuls'; -import { MainSca } from '../../agents/sca'; -import { MainMitre } from './main-mitre'; -import WzReduxProvider from '../../../redux/wz-redux-provider'; -import { ComplianceTable } from '../../overview/compliance-table'; -import { withAgentSupportModule } from '../../../components/common/hocs'; import { connect } from 'react-redux'; -import { compose } from 'redux'; +import { getDataPlugin } from '../../../kibana-services'; -import { ModuleMitreAttackIntelligence } from '../../overview/mitre_attack_intelligence'; +const mapStateToProps = (state) => ({ + agent: state.appStateReducers.currentAgentData, +}); -export class MainModuleOverview extends Component { +export const MainModuleOverview = connect(mapStateToProps)(class MainModuleOverview extends Component { constructor(props) { super(props); this.reportingService = new ReportingService(); @@ -132,71 +125,32 @@ export class MainModuleOverview extends Component { } this.setGlobalBreadcrumb(); + const { filterManager } = getDataPlugin().query; + this.filterManager = filterManager; } render() { const { section, selectView } = this.props; + const ModuleTabView = this.props.tabs.find(tab => tab.id === selectView); return (
- -
- {this.props.tabs && this.props.tabs.length && ( -
- - {this.props.renderTabs()} - - - - - - {selectView === 'dashboard' && this.props.renderReportButton()} - {(this.props.buttons || []).includes('dashboard') && - this.props.renderDashboardButton()} - -
- )} -
- -
+
+ {this.props.tabs && this.props.tabs.length && ( +
+ + {this.props.renderTabs()} + + + {ModuleTabView && ModuleTabView.buttons && ModuleTabView.buttons.map((ModuleViewButton, index) => + typeof ModuleViewButton !== 'string' ? : null)} + + + +
+ )} +
+ {ModuleTabView && ModuleTabView.component && }
); } -} - -const mapStateToProps = (state) => ({ - agent: state.appStateReducers.currentAgentData, -}); - -const ModuleTabViewer = compose( - connect(mapStateToProps), - withAgentSupportModule -)((props) => { - const { section, selectView } = props; - return ( - <> - {selectView === 'events' && } - {selectView === 'loader' && ( - props.loadSection(section)} - redirect={props.afterLoad} - > - )} - {selectView === 'dashboard' && } - {selectView === 'settings' && } - - {/* ---------------------MODULES WITH CUSTOM PANELS--------------------------- */} - {section === 'fim' && selectView === 'inventory' && } - {section === 'sca' && selectView === 'inventory' && } - - {section === 'vuls' && selectView === 'inventory' && } - - {section === 'mitre' && selectView === 'inventory' && } - {section === 'mitre' && selectView === 'intelligence' && } - {['pci', 'gdpr', 'hipaa', 'nist', 'tsc'].includes(section) && selectView === 'inventory' && ( - props.onSelectedTabChanged(id)} /> - )} - {/* -------------------------------------------------------------------------- */} - - ); -}); \ No newline at end of file +}) diff --git a/public/components/common/modules/main.tsx b/public/components/common/modules/main.tsx index 9f0e853d2d..1793af4d17 100644 --- a/public/components/common/modules/main.tsx +++ b/public/components/common/modules/main.tsx @@ -21,12 +21,10 @@ import { } from '@elastic/eui'; import '../../common/modules/module.scss'; import { ReportingService } from '../../../react-services/reporting'; -import { AppNavigate } from '../../../react-services/app-navigate'; import { ModulesDefaults } from './modules-defaults'; -import { getAngularModule, getDataPlugin, getUiSettings } from '../../../kibana-services'; +import { getAngularModule, getDataPlugin } from '../../../kibana-services'; import { MainModuleAgent } from './main-agent' import { MainModuleOverview } from './main-overview'; -import store from '../../../redux/store'; import { compose } from 'redux'; import { withReduxProvider,withErrorBoundary } from '../hocs'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; @@ -49,18 +47,12 @@ export const MainModule = compose( }; const app = getAngularModule(); this.$rootScope = app.$injector.get('$rootScope'); - } - - async componentDidMount() { if (!(ModulesDefaults[this.props.section] || {}).notModule) { this.tabs = (ModulesDefaults[this.props.section] || {}).tabs || [ { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }, ]; - this.buttons = (ModulesDefaults[this.props.section] || {}).buttons || [ - 'reporting', - 'settings', - ]; + this.module = ModulesDefaults[this.props.section]; } } @@ -71,17 +63,6 @@ export const MainModule = compose( } } - canBeInit(tab) { - //checks if the init table can be set - let canInit = false; - this.tabs.forEach((element) => { - if (element.id === tab && (!element.onlyAgent || (element.onlyAgent && this.props.agent))) { - canInit = true; - } - }); - return canInit; - } - renderTabs(agent = false) { const { selectView } = this.state; if (!agent) { @@ -107,114 +88,6 @@ export const MainModule = compose( ); } - startVis2PngByAgent = async () => { - const agent = - (this.props.agent || store.getState().appStateReducers.currentAgentData || {}).id || false; - await this.reportingService.startVis2Png(this.props.section, agent); - }; - - async startReport() { - try { - this.setState({ loadingReport: true }); - const isDarkModeTheme = getUiSettings().get('theme:darkMode'); - if (isDarkModeTheme) { - - //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'); - - try { - $labels.css('color', 'black'); - $vizBackground.css('background-color', 'transparent'); - await this.startVis2PngByAgent(); - $vizBackground.css('background-color', defaultVizBackground); - $labels.css('color', defaultTextColor); - } catch (error) { - $labels.css('color', defaultTextColor); - $vizBackground.css('background-color', defaultVizBackground); - const options = { - context: `${MainModule.name}.startReport`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: 'Error generating the report', - }, - }; - getErrorOrchestrator().handleError(options); - this.setState({ loadingReport: false }); - } - } else { - await this.startVis2PngByAgent(); - } - } finally { - this.setState({ loadingReport: false }); - } - } - - renderReportButton() { - return ( - (this.props.disabledReport && ( - - - this.startReport()} - > - Generate report - - - - )) || ( - - this.startReport()} - > - Generate report - - - ) - ); - } - - renderDashboardButton() { - const href = `#/overview?tab=${this.props.section}&agentId=${this.props.agent.id}`; - return ( - - this.onSelectedTabChanged('dashboard')} - > - Dashboard - - - ); - } - - renderSettingsButton() { - return ( - - this.onSelectedTabChanged('settings')} - > - Configuration - - - ); - } - loadSection(id) { this.setState({ selectView: id }); } @@ -231,8 +104,7 @@ export const MainModule = compose( new RegExp('tabView=' + '[^&]*'), `tabView=${id === 'events' ? 'discover' : id === 'inventory' ? 'inventory' : 'panels'}` ); - this.afterLoad = id; - this.loadSection('loader'); + this.loadSection(id === 'panels' ? 'dashboard' : id === 'discover' ? 'events' : id); } else { this.loadSection(id === 'panels' ? 'dashboard' : id === 'discover' ? 'events' : id); } @@ -244,13 +116,9 @@ export const MainModule = compose( const { selectView } = this.state; const mainProps = { selectView, - afterLoad: this.afterLoad, - buttons: this.buttons, tabs: this.tabs, + module: this.module, renderTabs: () => this.renderTabs(), - renderReportButton: () => this.renderReportButton(), - renderDashboardButton: () => this.renderDashboardButton(), - renderSettingsButton: () => this.renderSettingsButton(), loadSection: (id) => this.loadSection(id), onSelectedTabChanged: (id) => this.onSelectedTabChanged(id), }; diff --git a/public/components/common/modules/modules-defaults.js b/public/components/common/modules/modules-defaults.js index 0a7232f2c2..d5783601dd 100644 --- a/public/components/common/modules/modules-defaults.js +++ b/public/components/common/modules/modules-defaults.js @@ -9,66 +9,123 @@ * * Find more information about this on the LICENSE file. */ +import { Dashboard } from './dashboard'; +import { Events } from './events'; +import { MainFim } from '../../agents/fim'; +import { MainSca } from '../../agents/sca'; +import { MainVuls } from '../../agents/vuls'; +import { MainMitre } from './main-mitre'; +import { ModuleMitreAttackIntelligence } from '../../overview/mitre_attack_intelligence'; +import { ComplianceTable } from '../../overview/compliance-table'; +import ButtonModuleExploreAgent from '../../../controllers/overview/components/overview-actions/overview-actions'; +import { ButtonModuleGenerateReport } from '../modules/buttons'; + +const DashboardTab = { id: 'dashboard', name: 'Dashboard', buttons: [ButtonModuleExploreAgent, ButtonModuleGenerateReport], component: Dashboard}; +const EventsTab = { id: 'events', name: 'Events', buttons: [ButtonModuleExploreAgent], component: Events }; +const RegulatoryComplianceTabs = [{ id: 'inventory', name: 'Controls', buttons: [ButtonModuleExploreAgent], component: ComplianceTable }, DashboardTab, EventsTab]; + export const ModulesDefaults = { general: { init: 'dashboard', - tabs: [{ id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] }, fim: { init: 'dashboard', - tabs: [{ id: 'inventory', name: 'Inventory', onlyAgent: false }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting', 'settings'] + tabs: [{ id: 'inventory', name: 'Inventory', buttons: [ButtonModuleExploreAgent], component: MainFim }, DashboardTab, EventsTab], + availableFor: ['manager','agent'] + }, + aws: { + init: 'dashboard', + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'], }, gcp: { init: 'dashboard', - tabs: [{ id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] + }, + pm: { + init: 'dashboard', + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] + }, + audit: { + init: 'dashboard', + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] }, sca: { init: 'inventory', - tabs: [{ id: 'inventory', name: 'Inventory' }, { id: 'events', name: 'Events' }], - buttons: ['settings'] + tabs: [{ id: 'inventory', name: 'Inventory', buttons: [ButtonModuleExploreAgent], component: MainSca }, EventsTab], + buttons: ['settings'], + availableFor: ['manager','agent'] }, - mitre: { + ciscat: { init: 'dashboard', - tabs: [{id: 'intelligence', name: 'Intelligence'}, { id: 'inventory', name: 'Framework' }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] }, vuls: { init: 'dashboard', - tabs: [{ id: 'inventory', name: 'Inventory', onlyAgent: false }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting', 'settings'] + tabs: [{ id: 'inventory', name: 'Inventory', buttons: [ButtonModuleExploreAgent], component: MainVuls }, DashboardTab, EventsTab], + buttons: ['settings'], + availableFor: ['manager','agent'] + }, + mitre: { + init: 'dashboard', + tabs: [{ id: 'intelligence', name: 'Intelligence', component: ModuleMitreAttackIntelligence }, { id: 'inventory', name: 'Framework', buttons: [ButtonModuleExploreAgent], component: MainMitre }, DashboardTab, EventsTab], + availableFor: ['manager','agent'] }, virustotal: { init: 'dashboard', - tabs: [{ id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] + }, + docker: { + init: 'dashboard', + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] + }, + pci: { + init: 'dashboard', + tabs: RegulatoryComplianceTabs, + availableFor: ['manager','agent'] + }, + osquery: { + init: 'dashboard', + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] + }, + oscap: { + init: 'dashboard', + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] }, pci: { init: 'dashboard', - tabs: [{ id: 'inventory', name: 'Controls' }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: [DashboardTab, EventsTab], + availableFor: ['manager','agent'] }, hipaa: { init: 'dashboard', - tabs: [{ id: 'inventory', name: 'Controls' }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: RegulatoryComplianceTabs, + availableFor: ['manager','agent'] }, nist: { init: 'dashboard', - tabs: [{ id: 'inventory', name: 'Controls' }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: RegulatoryComplianceTabs, + availableFor: ['manager','agent'] }, gdpr: { init: 'dashboard', - tabs: [{ id: 'inventory', name: 'Controls' }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: RegulatoryComplianceTabs, + availableFor: ['manager','agent'] }, tsc: { init: 'dashboard', - tabs: [{ id: 'inventory', name: 'Controls' }, { id: 'dashboard', name: 'Dashboard' }, { id: 'events', name: 'Events' }], - buttons: ['reporting'] + tabs: RegulatoryComplianceTabs, + availableFor: ['manager','agent'] }, syscollector: { notModule: true diff --git a/public/components/common/modules/settings.tsx b/public/components/common/modules/settings.tsx deleted file mode 100644 index 26045f995c..0000000000 --- a/public/components/common/modules/settings.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Wazuh app - Integrity monitoring components - * Copyright (C) 2015-2021 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 React, { Component } from 'react'; -import { EuiSpacer, EuiTitle, EuiPanel, EuiPage } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import WzBadge from '../../../controllers/management/components/management/configuration/util-components/badge' -import WzReduxProvider from '../../../redux/wz-redux-provider'; -import WzConfigurationIntegrityMonitoring from '../../../controllers/management/components/management/configuration/integrity-monitoring/integrity-monitoring'; -import WzConfigurationPolicyMonitoring from '../../../controllers/management/components/management/configuration/policy-monitoring/policy-monitoring'; -import WzConfigurationOpenSCAP from '../../../controllers/management/components/management/configuration/open-scap/open-scap'; -import WzConfigurationCisCat from '../../../controllers/management/components/management/configuration/cis-cat/cis-cat'; -import WzConfigurationVulnerabilities from '../../../controllers/management/components/management/configuration/vulnerabilities/vulnerabilities'; -import WzConfigurationOsquery from '../../../controllers/management/components/management/configuration/osquery/osquery'; -import WzConfigurationDockerListener from '../../../controllers/management/components/management/configuration/docker-listener/docker-listener'; - -type SettingsPropTypes = { - agent: { id: string }, - clusterNodeSelected?: string -} - -type SettingsState = { - badge: boolean | null -} -export class Settings extends Component { - constructor(props) { - super(props); - this.state = { - badge: null - } - } - updateBadge(badge) { - this.setState({ badge }) - } - render() { - const { badge } = this.state; - const { section } = this.props; - return ( - - - - - {i18n.translate('wazuh.configuration', { defaultMessage: 'Configuration' })} {typeof badge === 'boolean' ? - : null} - - - - {section === 'fim' && this.updateBadge(e)} />} - {(section === 'pm' || section === 'sca' || section === 'audit') && - this.updateBadge(e)} onlyShowTab={section === 'pm' ? 'Policy Monitoring' : section === 'audit' ? 'System audit' : section === 'sca' ? 'SCA': undefined}/>} - {section === 'oscap' && this.updateBadge(e)} />} - {section === 'ciscat' && this.updateBadge(e)} />} - {section === 'vuls' && this.updateBadge(e)} />} - {section === 'osquery' && this.updateBadge(e)} />} - {section === 'docker' && this.updateBadge(e)} />} - - - - ) - } -} diff --git a/public/components/overview/compliance-table/compliance-table.tsx b/public/components/overview/compliance-table/compliance-table.tsx index 2f3430708e..ca95757668 100644 --- a/public/components/overview/compliance-table/compliance-table.tsx +++ b/public/components/overview/compliance-table/compliance-table.tsx @@ -13,7 +13,6 @@ import React, { Component } from 'react'; import { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SearchBar, FilterManager } from '../../../../../../src/plugins/data/public/'; -import { I18nProvider } from '@kbn/i18n/react'; //@ts-ignore import { ComplianceRequirements } from './components/requirements'; import { ComplianceSubrequirements } from './components/subrequirements'; @@ -28,8 +27,9 @@ import { getDataPlugin } from '../../../kibana-services'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { withAgentSupportModule } from '../../common/hocs'; -export class ComplianceTable extends Component { +export const ComplianceTable = withAgentSupportModule(class ComplianceTable extends Component { _isMount = false; timefilter: { getTime(): any; @@ -331,4 +331,4 @@ export class ComplianceTable extends Component { ); } -} +}) diff --git a/public/controllers/overview/components/overview-actions/overview-actions.js b/public/controllers/overview/components/overview-actions/overview-actions.js index b5c4a60127..c92715e2b8 100644 --- a/public/controllers/overview/components/overview-actions/overview-actions.js +++ b/public/controllers/overview/components/overview-actions/overview-actions.js @@ -10,22 +10,18 @@ * Find more information about this on the LICENSE file. */ import React, { Component } from 'react'; -import store from '../../../../redux/store'; import { connect } from 'react-redux'; import { showExploreAgentModal, updateCurrentAgentData } from '../../../../redux/actions/appStateActions'; import { - EuiButtonEmpty, - EuiButtonIcon, - EuiFlexItem, - EuiIcon, EuiOverlayMask, EuiOutsideClickDetector, EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, - EuiToolTip, + EuiPopover, } from '@elastic/eui'; +import { WzButton } from '../../../../components/common/buttons'; import './agents-selector.scss'; import { AgentSelectionTable } from './agents-selection-table'; import { WAZUH_ALERTS_PATTERN } from '../../../../../common/constants'; @@ -53,21 +49,20 @@ class OverviewActions extends Component { } componentDidMount() { - const agentId = store.getState().appStateReducers.currentAgentData.id; + const { filterManager } = getDataPlugin().query; this.setState({ filterManager: filterManager }, () => { if (this.props.initialFilter) this.agentTableSearch([this.props.initialFilter]) - if (agentId) this.agentTableSearch([agentId]) + if (this.props.agent.id) this.agentTableSearch([this.props.agent.id]) }); } componentDidUpdate(){ - const agent = store.getState().appStateReducers.currentAgentData; - if(this.state.isAgent && !agent.id){ + if(this.state.isAgent && !this.props.agent.id){ this.setState({isAgent: false}) - }else if(agent.id && this.state.isAgent !== agent.id){ - this.setState({isAgent: agent.id}) + }else if(this.props.agent.id && this.state.isAgent !== this.props.agent.id){ + this.setState({isAgent: this.props.agent.id}) } } @@ -86,7 +81,7 @@ class OverviewActions extends Component { closeAgentModal() { this.setState({ isAgentModalVisible: false }); - store.dispatch(showExploreAgentModal(false)); + this.props.showExploreAgentModal(false); } showAgentModal() { @@ -169,43 +164,56 @@ class OverviewActions extends Component { ); } - const agent = store.getState().appStateReducers.currentAgentData; + + const thereAgentSelected = (this.props.agent || {}).id + + const avaliableForAgent = this.props.module.availableFor && this.props.module.availableFor.includes('agent'); + + let buttonUnpinAgent, buttonExploreAgent; + if(thereAgentSelected){ + buttonUnpinAgent = ( + { + this.props.updateCurrentAgentData({}); + this.removeAgentsFilter(); + }} + tooltip={{position: 'bottom', content: 'Unpin agent'}} + aria-label='Unpin agent' + /> + ); + }; + + buttonExploreAgent = ( + this.showAgentModal()}> + {thereAgentSelected ? `${this.props.agent.name} (${this.props.agent.id})` : 'Explore agent'} + + ) + return ( -
- - {!this.state.isAgent && ( - - this.showAgentModal()}> -   Explore agent - - - )} - {this.state.isAgent && ( -
- - this.showAgentModal()}> - {agent.name} ({agent.id}) - - - - { - store.dispatch(updateCurrentAgentData({})); - this.removeAgentsFilter(); - }} - aria-label='Unpin agent' /> - -
- )} -
+
+ {buttonExploreAgent} + {thereAgentSelected && ( + !avaliableForAgent && ( + {}}> + This module is not supported for agents. Remove the pinned agent. + + + ) || buttonUnpinAgent + )} {modal}
); @@ -215,7 +223,13 @@ class OverviewActions extends Component { const mapStateToProps = state => { return { state: state.appStateReducers, + agent: state.appStateReducers.currentAgentData }; }; -export default connect(mapStateToProps, null)(OverviewActions); +const mapDispatchToProps = dispatch => ({ + updateCurrentAgentData: (agent) => dispatch(updateCurrentAgentData(agent)), + showExploreAgentModal: (data) => dispatch(showExploreAgentModal(data)) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(OverviewActions); diff --git a/public/react-services/reporting.js b/public/react-services/reporting.js index 2508a7a7db..3c227159d5 100644 --- a/public/react-services/reporting.js +++ b/public/react-services/reporting.js @@ -79,17 +79,21 @@ export class ReportingService { idArray = rawVisualizations.map((item) => item.id); } + const visualizationIDList = []; for (const item of idArray) { const tmpHTMLElement = $(`#${item}`); - this.vis2png.assignHTMLItem(item, tmpHTMLElement); + if(tmpHTMLElement[0]){ + this.vis2png.assignHTMLItem(item, tmpHTMLElement); + visualizationIDList.push(item); + } } const appliedFilters = await this.visHandlers.getAppliedFilters(syscollectorFilters); - const array = await this.vis2png.checkArray(idArray); - const name = `wazuh-${agents ? `agent-${agents}` : 'overview'}-${tab}-${ - (Date.now() / 1000) | 0 - }.pdf`; + const array = await this.vis2png.checkArray(visualizationIDList); + const name = `wazuh-${ + agents ? `agent-${agents}` : 'overview' + }-${tab}-${(Date.now() / 1000) | 0}.pdf`; const browserTimezone = moment.tz.guess(true);