diff --git a/plugins/main/public/components/agents/prompt-agent-no-support-module.tsx b/plugins/main/public/components/agents/prompt-agent-no-support-module.tsx index f2efab7a36..876ba2af8d 100644 --- a/plugins/main/public/components/agents/prompt-agent-no-support-module.tsx +++ b/plugins/main/public/components/agents/prompt-agent-no-support-module.tsx @@ -13,20 +13,20 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; -import { showExploreAgentModal } from '../../redux/actions/appStateActions'; +import { showExploreAgentModalGlobal } from '../../redux/actions/appStateActions'; export const PromptAgentNoSupportModule = () => { const dispatch = useDispatch(); - const openAgentSelector = () => dispatch(showExploreAgentModal(true)); + const openAgentSelector = () => dispatch(showExploreAgentModalGlobal(true)); return ( Module not supported by the agent} actions={ - + Select agent } /> - ) -} \ No newline at end of file + ); +}; diff --git a/plugins/main/public/components/agents/prompts/index.ts b/plugins/main/public/components/agents/prompts/index.ts index 33d6139912..9054cbdbb1 100644 --- a/plugins/main/public/components/agents/prompts/index.ts +++ b/plugins/main/public/components/agents/prompts/index.ts @@ -15,3 +15,4 @@ export * from './prompt-no-selected-agent'; export * from './prompt-select-agent'; export * from './prompt-agent-feature-version'; export * from './prompt_module_not_for_agent'; +export * from './prompt-agent-never-connected'; diff --git a/plugins/main/public/components/agents/prompts/prompt-agent-never-connected.tsx b/plugins/main/public/components/agents/prompts/prompt-agent-never-connected.tsx new file mode 100644 index 0000000000..fb7c348c36 --- /dev/null +++ b/plugins/main/public/components/agents/prompts/prompt-agent-never-connected.tsx @@ -0,0 +1,54 @@ +/* + * Wazuh app - Prompt when status agent is Never connected. + * 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 React, { Fragment } from 'react'; +import { EuiEmptyPrompt, EuiButton, EuiLink } from '@elastic/eui'; +import { useDispatch } from 'react-redux'; +import { webDocumentationLink } from '../../../../common/services/web_documentation'; +import { showExploreAgentModalGlobal } from '../../../redux/actions/appStateActions'; + +const documentationLink = webDocumentationLink( + 'user-manual/agents/agent-connection.html', +); + +export const PromptAgentNeverConnected = () => { + const dispatch = useDispatch(); + const openAgentSelector = () => dispatch(showExploreAgentModalGlobal(true)); + return ( + Agent has never connected.} + body={ + +

+ The agent has been registered but has not yet connected to the + manager. +

+ + Checking connection with the Wazuh server + +
+ } + actions={ + + Select agent + + } + /> + ); +}; diff --git a/plugins/main/public/components/agents/prompts/prompt-select-agent.tsx b/plugins/main/public/components/agents/prompts/prompt-select-agent.tsx index bcae26d080..ff64c41039 100644 --- a/plugins/main/public/components/agents/prompts/prompt-select-agent.tsx +++ b/plugins/main/public/components/agents/prompts/prompt-select-agent.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { showExploreAgentModal } from '../../../redux/actions/appStateActions'; +import { showExploreAgentModalGlobal } from '../../../redux/actions/appStateActions'; type PromptSelectAgentProps = { body?: string; @@ -22,14 +22,14 @@ type PromptSelectAgentProps = { export const PromptSelectAgent = ({ body, title }: PromptSelectAgentProps) => { const dispatch = useDispatch(); - const openAgentSelector = () => dispatch(showExploreAgentModal(true)); + const openAgentSelector = () => dispatch(showExploreAgentModalGlobal(true)); return ( {title}} body={body &&

{body}

} actions={ - + Select agent } diff --git a/plugins/main/public/components/agents/syscollector/inventory.tsx b/plugins/main/public/components/agents/syscollector/inventory.tsx index dfcf1fe798..f6060112e3 100644 --- a/plugins/main/public/components/agents/syscollector/inventory.tsx +++ b/plugins/main/public/components/agents/syscollector/inventory.tsx @@ -11,15 +11,7 @@ */ import React from 'react'; -import { - EuiEmptyPrompt, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiCallOut, - EuiLink, - EuiPanel, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiPanel } from '@elastic/eui'; import { InventoryMetrics } from './components/syscollector-metrics'; import { netaddrColumns, @@ -33,54 +25,23 @@ import { API_NAME_AGENT_STATUS, SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, } from '../../../../common/constants'; -import { webDocumentationLink } from '../../../../common/services/web_documentation'; import { TableWzAPI } from '../../common/tables'; import { WzRequest } from '../../../react-services'; import { get as getLodash } from 'lodash'; -import { getCore } from '../../../kibana-services'; +import { compose } from 'redux'; +import { withGuard } from '../../common/hocs'; +import { PromptAgentNeverConnected } from '../prompts'; const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); -export function SyscollectorInventory({ agent }) { - if (agent && agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED) { - return ( - Agent has never connected.} - body={ - <> -

- The agent has been registered but has not yet connected to the - manager. -

- - Checking connection with the Wazuh server - - - } - actions={ - - Back - - } - /> - ); - } - +export const SyscollectorInventory = compose( + withGuard( + props => + props.agent && + props.agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED, + PromptAgentNeverConnected, + ), +)(function SyscollectorInventory({ agent }) { let soPlatform; if (agent?.os?.uname?.includes('Linux')) { soPlatform = 'linux'; @@ -468,4 +429,4 @@ export function SyscollectorInventory({ agent }) { ); -} +}); diff --git a/plugins/main/public/components/common/welcome/agents-welcome.js b/plugins/main/public/components/common/welcome/agents-welcome.js index c538a4087c..e3112d7d7c 100644 --- a/plugins/main/public/components/common/welcome/agents-welcome.js +++ b/plugins/main/public/components/common/welcome/agents-welcome.js @@ -13,7 +13,6 @@ */ import React, { Component, Fragment } from 'react'; import { - EuiLink, EuiPanel, EuiFlexItem, EuiFlexGroup, @@ -22,12 +21,10 @@ import { EuiFlexGrid, EuiButtonEmpty, EuiPage, - EuiButton, EuiPopover, EuiLoadingChart, EuiToolTip, EuiButtonIcon, - EuiEmptyPrompt, EuiPageBody, } from '@elastic/eui'; import { @@ -37,7 +34,6 @@ import { RequirementVis, } from './components'; import { AgentInfo } from './agents-info'; -import store from '../../../redux/store'; import WzReduxProvider from '../../../redux/wz-redux-provider'; import MenuAgent from './components/menu-agent'; import './welcome.scss'; @@ -47,21 +43,32 @@ import { VisFactoryHandler } from '../../../react-services/vis-factory-handler'; import { AppState } from '../../../react-services/app-state'; import { FilterHandler } from '../../../utils/filter-handler'; import { TabVisualizations } from '../../../factories/tab-visualizations'; -import { updateCurrentAgentData } from '../../../redux/actions/appStateActions'; -import { getAngularModule, getChrome, getCore } from '../../../kibana-services'; +import { + showExploreAgentModalGlobal, + updateCurrentAgentData, +} from '../../../redux/actions/appStateActions'; +import { + getAngularModule, + getChrome, + getCore, + getDataPlugin, +} from '../../../kibana-services'; import { hasAgentSupportModule } from '../../../react-services/wz-agents'; import { withErrorBoundary, withGlobalBreadcrumb, + withGuard, withReduxProvider, } from '../hocs'; import { compose } from 'redux'; -import { - API_NAME_AGENT_STATUS, - WAZUH_AGENTS_OS_TYPE, -} from '../../../../common/constants'; -import { webDocumentationLink } from '../../../../common/services/web_documentation'; +import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; import { WAZUH_MODULES } from '../../../../common/wazuh-modules'; +import { + PromptAgentNeverConnected, + PromptNoSelectedAgent, +} from '../../agents/prompts'; +import { connect } from 'react-redux'; +import { WzButton } from '../buttons'; import { Applications, configurationAssessment, @@ -71,21 +78,44 @@ import { vulnerabilityDetection, } from '../../../utils/applications'; +const mapStateToProps = state => ({ + agent: state.appStateReducers.currentAgentData, +}); + +const mapDispatchToProps = dispatch => ({ + showExploreAgentModalGlobal: data => + dispatch(showExploreAgentModalGlobal(data)), + updateCurrentAgentData: data => dispatch(updateCurrentAgentData(data)), +}); + export const AgentsWelcome = compose( withReduxProvider, withErrorBoundary, + connect(mapStateToProps, mapDispatchToProps), withGlobalBreadcrumb(({ agent }) => { return [ { text: '' }, { text: 'IT Hygiene', }, - { - text: `${agent.name}`, - truncate: true, - }, + ...(agent?.name + ? [ + { + text: `${agent.name}`, + truncate: true, + }, + ] + : []), ]; }), + withGuard( + props => !(props.agent && props.agent.id), + () => , + ), + withGuard( + props => props.agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED, + PromptAgentNeverConnected, + ), )( class AgentsWelcome extends Component { _isMount = false; @@ -105,7 +135,7 @@ export const AgentsWelcome = compose( actionAgents: true, // Hide actions agents selectedRequirement: 'pci', menuAgent: [], - maxModules: 6, + maxModules: 5, widthWindow: window.innerWidth, isLocked: false, }; @@ -118,19 +148,23 @@ export const AgentsWelcome = compose( } else { menuSize = window.innerWidth - this.offset; } - let maxModules = 6; - if (menuSize > 1250) { - maxModules = 6; + let maxModules = 5; + if (menuSize > 1400) { + maxModules = 5; } else { - if (menuSize > 1100) { - maxModules = 5; + if (menuSize > 1250) { + maxModules = 4; } else { - if (menuSize > 900) { - maxModules = 4; - } else { + if (menuSize > 1100) { maxModules = 3; - if (menuSize < 750) { - maxModules = null; + } else { + if (menuSize > 900) { + maxModules = 2; + } else { + maxModules = 1; + if (menuSize < 750) { + maxModules = null; + } } } } @@ -139,9 +173,24 @@ export const AgentsWelcome = compose( this.setState({ maxModules: maxModules, widthWindow: window.innerWidth }); }; + /* TODO: we should to create a unique Explore agent button instead + of duplicating it. It was duplicated due to the differences of requirements + in the Explore agent button for the modules and agent welcome + */ + async removeAgentsFilter() { + await this.props.setAgent(false); + const currentAppliedFilters = getDataPlugin().query.filterManager.filters; + const agentFilters = currentAppliedFilters.filter(x => { + return x.meta.key !== 'agent.id'; + }); + getDataPlugin().query.filterManager.setFilters(agentFilters); + } + async componentDidMount() { this._isMount = true; - store.dispatch(updateCurrentAgentData(this.props.agent)); + /* WORKAROUND: ensure the $scope.agent is synced with the agent stored in Redux (this.props.agent). See agents.js controller. + */ + this.props.setAgent(this.props.agent); this.updatePinnedApplications(); this.updateWidth(); const tabVisualizations = new TabVisualizations(); @@ -169,6 +218,13 @@ export const AgentsWelcome = compose( ); } + componentDidUpdate(prevProps) { + /* WORKAROUND: ensure the $scope.agent is synced with the agent stored in Redux (this.props.agent). See agents.js controller. + */ + if (prevProps.agent.id !== this.props.agent.id) { + this.props.setAgent(this.props.agent); + } + } componentWillUnmount() { this.drawerLokedSubscribtion?.unsubscribe(); } @@ -284,10 +340,15 @@ export const AgentsWelcome = compose( renderTitle() { const notNeedStatus = true; + const thereAreAgentSelected = Boolean(this.props.agent?.id); return ( - + - + {(this.state.maxModules !== null && this.renderModules()) || ( - + - + + {thereAreAgentSelected + ? `${this.props.agent.name} (${this.props.agent.id})` + : 'Explore agent'} + + {thereAreAgentSelected && ( + { + this.props.updateCurrentAgentData({}); + this.removeAgentsFilter(); + }} + tooltip={{ position: 'bottom', content: 'Unpin agent' }} + aria-label='Unpin agent' + /> + )} + + + + this.props.switchTab('syscollector', notNeedStatus) } + tooltip={ + this.state.maxModules === null + ? { position: 'bottom', content: 'Inventory data' } + : undefined + } > - Inventory data - + {this.state.maxModules !== null ? 'Inventory data' : ''} + - this.props.switchTab('stats', notNeedStatus)} + tooltip={ + this.state.maxModules === null + ? { position: 'bottom', content: 'Stats' } + : undefined + } > - Stats - + {this.state.maxModules !== null ? 'Stats' : ''} + - this.props.switchTab('configuration', notNeedStatus) } + tooltip={ + this.state.maxModules === null + ? { position: 'bottom', content: 'Configuration' } + : undefined + } > - Configuration - + {this.state.maxModules !== null ? 'Configuration' : ''} + @@ -381,19 +492,19 @@ export const AgentsWelcome = compose(

-

MITRE

+

MITRE ATT&CK

- + { getCore().application.navigateToApp('mitre-attack'); }} - aria-label='Open MITRE' + aria-label='Open MITRE ATT&CK' /> @@ -474,48 +585,9 @@ export const AgentsWelcome = compose( render() { const title = this.renderTitle(); - if (this.props.agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED) { - return ( - Agent has never connected.} - body={ - -

- The agent has been registered but has not yet connected to the - manager. -

- - Checking connection with the Wazuh server - -
- } - actions={ - - Back - - } - /> - ); - } - return (
-
+
{title}
diff --git a/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx b/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx index ca5fd719e5..99c7a48635 100644 --- a/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx +++ b/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx @@ -89,7 +89,7 @@ function FimTable({ agent }) { const timeFilter = useTimeFilter(); useEffect(() => { getFimAlerts(agent.id, timeFilter, sort).then(setFimAlerts); - }, [timeFilter, sort]); + }, [timeFilter, sort, agent.id]); return ( { + return await getMitreCount(agentId, timeFilter, undefined); +}; + +const getTechniques = async (agentId, timeFilter, tacticID) => { + return await getMitreCount(agentId, timeFilter, tacticID); +}; + +const MitreTopTacticsTactics = ({ + agentId, + renderEmpty, + setView, + setSelectedTactic, + timeFilter, +}) => { + const getData = useAsyncActionRunOnStart(getTacticsData, [ + agentId, + timeFilter, + ]); + + if (getData.running) { + return ( +
+ +
+ ); + } + + if (getData?.data?.length === 0) { + return renderEmpty(); + } + return ( + <> +
+ + + +

Top Tactics

+
+
+
+ + + {getData?.data?.map(tactic => ( + { + setView('techniques'); + setSelectedTactic(tactic.key); + }} + > + {tactic.key} + + ))} + + +
+ + ); +}; + +const MitreTopTacticsTechniques = ({ + agentId, + renderEmpty, + selectedTactic, + setView, + timeFilter, +}) => { + const getData = useAsyncActionRunOnStart(getTechniques, [ + agentId, + timeFilter, + selectedTactic, + ]); + + const [showTechniqueDetails, setShowTechniqueDetails] = useState(''); + + if (showTechniqueDetails) { + const onChangeFlyout = () => { + setShowTechniqueDetails(''); + }; + + const openDiscover = (e, techniqueID) => { + AppNavigate.navigateToModule(e, 'overview', { + tab: 'mitre', + tabView: 'discover', + filters: { 'rule.mitre.id': techniqueID }, + }); + }; + + const openDashboard = (e, techniqueID) => { + AppNavigate.navigateToModule(e, 'overview', { + tab: 'mitre', + tabView: 'dashboard', + filters: { 'rule.mitre.id': techniqueID }, + }); + }; + return ( + openDashboard(e, itemId)} + openDiscover={(e, itemId) => openDiscover(e, itemId)} + implicitFilters={[{ 'agent.id': agentId }]} + agentId={agentId} + onChangeFlyout={onChangeFlyout} + currentTechnique={showTechniqueDetails} + /> + ); + } + + if (getData.running) { + return ( +
+ +
+ ); + } + + if (getData?.data?.length === 0) { + return renderEmpty(); + } + return ( + <> + + + + { + setView('tactics'); + }} + iconType='sortLeft' + aria-label='Back Top Tactics' + /> + + +

{selectedTactic}

+
+
+
+ + + {getData.data.map(tactic => ( + { + setShowTechniqueDetails(tactic.key); + }} + > + {tactic.key} + + ))} + + + + ); +}; + +export const MitreTopTactics = ({ agentId }) => { + const [view, setView] = useState<'tactics' | 'techniques'>('tactics'); + const [selectedTactic, setSelectedTactic] = useState(''); + const { timeFilter } = useTimeFilter(); + + useEffect(() => { + setView('tactics'); + }, [agentId]); + + const renderEmpty = () => ( + No results} + body={ +

No MITRE ATT&CK results were found in the selected time range.

+ } + /> + ); + + if (view === 'tactics') { + return ( + + ); + } + + if (view === 'techniques') { + return ( + + ); + } +}; diff --git a/plugins/main/public/components/common/welcome/components/mitre_top/mitre_top.tsx b/plugins/main/public/components/common/welcome/components/mitre_top/mitre_top.tsx deleted file mode 100644 index e5c938aa3d..0000000000 --- a/plugins/main/public/components/common/welcome/components/mitre_top/mitre_top.tsx +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Wazuh app - React component information about MITRE top tactics. - * - * 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 React, { Component, Fragment } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiText, - EuiFacetButton, - EuiButtonIcon, - EuiLoadingChart, - EuiOverlayMask, - EuiOutsideClickDetector, - EuiEmptyPrompt, -} from '@elastic/eui'; -import { FlyoutTechnique } from '../../../../../components/overview/mitre/components/techniques/components/flyout-technique'; -import { getIndexPattern } from '../../../../../components/overview/mitre/lib'; -import { getMitreCount } from './lib'; -import { AppNavigate } from '../../../../../react-services/app-navigate'; -import { getDataPlugin } from '../../../../../kibana-services'; - -export class MitreTopTactics extends Component { - _isMount = false; - - PluginPlatformServices: { [key: string]: any }; - timefilter: any; - indexPattern: any; - props!: { - [key: string]: any; - }; - state: { - alertsCount: { key: string; doc_count: number }[]; - isLoading: boolean; - time: any; - filterParams: object; - selectedTactic: string | undefined; - flyoutOn: boolean; - selectedTechnique: string; - }; - subscription: any; - - constructor(props) { - super(props); - this.PluginPlatformServices = getDataPlugin().query; - this.timefilter = this.PluginPlatformServices.timefilter.timefilter; - this.state = { - alertsCount: [], - isLoading: true, - time: this.timefilter.getTime(), - selectedTactic: undefined, - flyoutOn: false, - selectedTechnique: '', - }; - } - - async componentDidMount() { - this._isMount = true; - this.subscription = this.timefilter - .getTimeUpdate$() - .subscribe( - () => this._isMount && this.setState({ time: this.timefilter.getTime(), isLoading: true }) - ); - this.indexPattern = await getIndexPattern(); - getMitreCount(this.props.agentId, this.timefilter.getTime(), undefined).then((alertsCount) => - this.setState({ alertsCount, isLoading: false }) - ); - } - - async componentWillUnmount() { - this._isMount = false; - this.subscription.unsubscribe(); - } - - shouldComponentUpdate(nextProp, nextState) { - const { selectedTactic, isLoading, alertsCount } = this.state; - if (nextState.selectedTactic !== selectedTactic) return true; - if (!isLoading) return true; - if (JSON.stringify(nextState.alertsCount) !== JSON.stringify(alertsCount)) return true; - return false; - } - - async componentDidUpdate() { - const { selectedTactic, isLoading } = this.state; - if (isLoading) { - getMitreCount(this.props.agentId, this.timefilter.getTime(), selectedTactic).then( - (alertsCount) => { - if (alertsCount.length === 0) { - this.setState({ selectedTactic: undefined, isLoading: false }); - } - this.setState({ alertsCount, isLoading: false }); - } - ); - } - } - - renderLoadingStatus() { - const { isLoading } = this.state; - if (!isLoading) return; - return ( -
- -
- ); - } - - renderTacticsTop() { - const { alertsCount, isLoading } = this.state; - if (isLoading || alertsCount.length === 0) return; - return ( - -
- - - -

Top Tactics

-
-
-
- - - {alertsCount.map((tactic) => ( - { - this.setState({ - selectedTactic: tactic.key, - isLoading: true, - }); - }} - > - {tactic.key} - - ))} - - -
-
- ); - } - - renderTechniques() { - const { selectedTactic, alertsCount, isLoading } = this.state; - if (isLoading) return; - return ( - - - - - { - this.setState({ - selectedTactic: undefined, - isLoading: true, - flyoutOn: false, - }); - }} - iconType="sortLeft" - aria-label="Back Top Tactics" - /> - - -

{selectedTactic}

-
-
-
- - - {alertsCount.map((tactic) => ( - this.showFlyout(tactic.key)} - > - {tactic.key} - - ))} - - -
- ); - } - - renderEmptyPrompt() { - const { isLoading } = this.state; - if (isLoading) return; - return ( - No results} - body={ - -

No Mitre results were found in the selected time range.

-
- } - /> - ); - } - - onChangeFlyout = (flyoutOn) => { - this.setState({ flyoutOn }); - }; - - closeFlyout() { - this.setState({ flyoutOn: false }); - } - - showFlyout(tactic) { - this.setState({ - selectedTechnique: tactic, - flyoutOn: true, - }); - } - - openDiscover(e, techniqueID) { - AppNavigate.navigateToModule(e, 'overview', { - tab: 'mitre', - tabView: 'discover', - filters: { 'rule.mitre.id': techniqueID }, - }); - } - - openDashboard(e, techniqueID) { - AppNavigate.navigateToModule(e, 'overview', { - tab: 'mitre', - tabView: 'dashboard', - filters: { 'rule.mitre.id': techniqueID }, - }); - } - - render() { - const { flyoutOn, selectedTactic, selectedTechnique, alertsCount } = this.state; - const tacticsTop = this.renderTacticsTop(); - const tecniquesTop = this.renderTechniques(); - const loading = this.renderLoadingStatus(); - const emptyPrompt = this.renderEmptyPrompt(); - return ( - - {loading} - {!selectedTactic || alertsCount.length === 0 ? tacticsTop : tecniquesTop} - {alertsCount.length === 0 && emptyPrompt} - {flyoutOn && ( - this.openDashboard(e, itemId)} - openDiscover={(e, itemId) => this.openDiscover(e, itemId)} - implicitFilters={[{ 'agent.id': this.props.agentId }]} - agentId={this.props.agentId} - onChangeFlyout={this.onChangeFlyout} - currentTechnique={selectedTechnique} - /> - )} - - ); - } -} diff --git a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js index d95b595073..e7f600e6b8 100644 --- a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js +++ b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js @@ -17,7 +17,7 @@ import { EuiModal, EuiModalHeader, EuiModalBody, - EuiModalHeaderTitle + EuiModalHeaderTitle, } from '@elastic/eui'; import { connect } from 'react-redux'; import { showExploreAgentModalGlobal } from '../../redux/actions/appStateActions'; @@ -26,13 +26,12 @@ import { AgentSelectionTable } from '../../controllers/overview/components/overv import { getSettingDefaultValue } from '../../../common/services/settings'; import { AppState } from '../../react-services/app-state'; import { getAngularModule, getDataPlugin } from '../../kibana-services'; +import { getServices } from '../../kibana-integrations/discover/kibana_services'; class WzAgentSelector extends Component { constructor(props) { super(props); - this.state = { - - }; + this.state = {}; this.store = store; } @@ -42,18 +41,33 @@ class WzAgentSelector extends Component { this.location = $injector.get('$location'); } - closeAgentModal(){ + closeAgentModal() { store.dispatch(showExploreAgentModalGlobal(false)); } - agentTableSearch(agentIdList){ + agentTableSearch(agentIdList) { this.closeAgentModal(); - if(window.location.href.includes("/agents?")){ - this.location.search('agent', store.getState().appStateReducers.currentAgentData.id ? String(store.getState().appStateReducers.currentAgentData.id):null); + if (window.location.href.includes('/agents?')) { + this.location.search( + 'agent', + store.getState().appStateReducers.currentAgentData.id + ? String(store.getState().appStateReducers.currentAgentData.id) + : null, + ); this.route.reload(); return; } - this.location.search('agentId', store.getState().appStateReducers.currentAgentData.id ? String(store.getState().appStateReducers.currentAgentData.id):null); + + // This timeout is required because modifiying the filters on the filter manager changes the + // URL too and could cause the agentId parameter is not present. + setTimeout(() => { + this.location.search( + window.location.href.includes('/agents') ? 'agent' : 'agentId', + store.getState().appStateReducers.currentAgentData.id + ? String(store.getState().appStateReducers.currentAgentData.id) + : null, + ); + }, 1); const { filterManager } = getDataPlugin().query; if (agentIdList && agentIdList.length) { @@ -63,24 +77,25 @@ class WzAgentSelector extends Component { return x.meta.key !== 'agent.id'; }); const filter = { - "meta": { - "alias": null, - "disabled": false, - "key": "agent.id", - "negate": false, - "params": { "query": agentIdList[0] }, - "type": "phrase", - "index": AppState.getCurrentPattern() || getSettingDefaultValue('pattern') + meta: { + alias: null, + disabled: false, + key: 'agent.id', + negate: false, + params: { query: agentIdList[0] }, + type: 'phrase', + index: + AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), }, - "query": { - "match": { + query: { + match: { 'agent.id': { query: agentIdList[0], - type: 'phrase' - } - } + type: 'phrase', + }, + }, }, - "$state": { "store": "appState", "isImplicit": true}, + $state: { store: 'appState', isImplicit: true }, }; agentFilters.push(filter); filterManager.setFilters(agentFilters); @@ -88,13 +103,8 @@ class WzAgentSelector extends Component { } } - removeAgentsFilter(shouldUpdate){ + removeAgentsFilter() { this.closeAgentModal(); - if(window.location.href.includes("/agents?")){ - window.location.href = "#/agents-preview" - this.route.reload(); - return; - } const { filterManager } = getServices(); const currentAppliedFilters = filterManager.filters; const agentFilters = currentAppliedFilters.filter(x => { @@ -103,28 +113,39 @@ class WzAgentSelector extends Component { agentFilters.map(x => { filterManager.removeFilter(x); }); - this.closeAgentModal(); + + setTimeout(() => { + // This removes the search parameter in the URL. + // agent is used by IT Hygiene + // agentId is used by the alert modules + ['agent', 'agentId'].forEach(param => { + if (this.location.search()[param]) { + this.location.search(param, null); + } + }); + }, 1); } - getSelectedAgents(){ + getSelectedAgents() { const selectedAgents = {}; const agentId = store.getState().appStateReducers.currentAgentData.id; - if(agentId) - selectedAgents[agentId] = true; + if (agentId) selectedAgents[agentId] = true; return selectedAgents; } render() { - let modal = (<>); + let modal = <>; if (this.props.state.showExploreAgentModalGlobal) { modal = ( - this.closeAgentModal()}> + this.closeAgentModal()} + > this.closeAgentModal()} - initialFocus="[name=popswitch]" + initialFocus='[name=popswitch]' > Explore agent @@ -132,8 +153,12 @@ class WzAgentSelector extends Component { this.agentTableSearch(agentsIdList)} - removeAgentsFilter={(shouldUpdate) => this.removeAgentsFilter(shouldUpdate)} + updateAgentSearch={agentsIdList => + this.agentTableSearch(agentsIdList) + } + removeAgentsFilter={shouldUpdate => + this.removeAgentsFilter(shouldUpdate) + } selectedAgents={this.getSelectedAgents()} > @@ -148,11 +173,8 @@ class WzAgentSelector extends Component { const mapStateToProps = state => { return { - state: state.appStateReducers + state: state.appStateReducers, }; }; -export default connect( - mapStateToProps, - null -)(WzAgentSelector); +export default connect(mapStateToProps, null)(WzAgentSelector); diff --git a/plugins/main/public/controllers/agent/agents.js b/plugins/main/public/controllers/agent/agents.js index 05a50efba1..1daf8826fd 100644 --- a/plugins/main/public/controllers/agent/agents.js +++ b/plugins/main/public/controllers/agent/agents.js @@ -27,6 +27,8 @@ 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 { getSettingDefaultValue } from '../../../common/services/settings'; +import { updateCurrentAgentData } from '../../redux/actions/appStateActions'; +import store from '../../redux/store'; export class AgentsController { /** @@ -93,6 +95,11 @@ export class AgentsController { false, false, ]; + + this.loadWelcomeCardsProps(); + this.$scope.getWelcomeCardsProps = resultState => { + return { ...this.$scope.welcomeCardsProps, resultState }; + }; } /** @@ -604,6 +611,24 @@ export class AgentsController { const id = this.commonData.checkLocationAgentId(newAgentId, globalAgent); + this.loadWelcomeCardsProps(); + this.$scope.getWelcomeCardsProps = resultState => { + return { ...this.$scope.welcomeCardsProps, resultState }; + }; + + if (!id) { + this.$scope.load = false; + // We set some properties used by the rendered component to work and allowing + // to manage when there is not selected agent. + await this.$scope.switchTab(this.$scope.tab, true); + this.loadWelcomeCardsProps(); + this.$scope.getWelcomeCardsProps = resultState => { + return { ...this.$scope.welcomeCardsProps, resultState }; + }; + this.$scope.$applyAsync(); + return; + } + const data = await WzRequest.apiReq('GET', `/agents`, { params: { agents_list: id, @@ -615,6 +640,15 @@ export class AgentsController { this.$scope.agent = agentInfo; if (!this.$scope.agent) return; + + // Sync the selected agent on Redux store + if ( + store.getState().appStateReducers.currentAgentData.id !== + this.$scope.agent.id + ) { + store.dispatch(updateCurrentAgentData(this.$scope.agent)); + } + if (agentInfo && this.$scope.agent.os) { this.$scope.agentOS = this.$scope.agent.os.name + ' ' + this.$scope.agent.os.version; @@ -660,6 +694,9 @@ export class AgentsController { return hasAgentSupportModule(this.$scope.agent, component); } + setAgent(agent) { + this.$scope.agent = agent; + } /** * Get available welcome cards after getting the agent */ @@ -668,6 +705,7 @@ export class AgentsController { switchTab: (tab, force) => this.switchTab(tab, force), agent: this.$scope.agent, api: AppState.getCurrentAPI(), + setAgent: agent => this.setAgent(agent), goGroups: (agent, group) => this.goGroups(agent, group), }; } diff --git a/plugins/main/public/controllers/management/components/management/configuration/configuration-no-agent.js b/plugins/main/public/controllers/management/components/management/configuration/configuration-no-agent.js deleted file mode 100644 index 953b28ea3d..0000000000 --- a/plugins/main/public/controllers/management/components/management/configuration/configuration-no-agent.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Wazuh app - Prompt when status agent is Never connected. - * 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 React, { Fragment } from 'react'; -import { EuiEmptyPrompt, EuiButton, EuiLink } from '@elastic/eui'; -import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; -import { getCore } from '../../../../../kibana-services'; - -const documentationLink = webDocumentationLink( - 'user-manual/agents/agent-connection.html', -); - -export const WzAgentNeverConnectedPrompt = () => ( - Agent has never connected.} - body={ - -

- The agent has been registered but has not yet connected to the - manager. -

- - Checking connection with the Wazuh server - -
- } - actions={ - - Back - - } - /> -); diff --git a/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js b/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js index 1fd9b9adb4..fbccffc50a 100644 --- a/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js +++ b/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js @@ -48,7 +48,6 @@ import WzViewSelector, { } from './util-components/view-selector'; import WzLoading from './util-components/loading'; import { withRenderIfOrWrapped } from './util-hocs/render-if'; -import { WzAgentNeverConnectedPrompt } from './configuration-no-agent'; import WzConfigurationPath from './util-components/configuration-path'; import WzRefreshClusterInfoButton from './util-components/refresh-cluster-info-button'; import { withUserAuthorizationPrompt } from '../../../../../components/common/hocs'; @@ -83,6 +82,7 @@ import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchest import { getErrorOrchestrator } from '../../../../../react-services/common-services'; import { WzConfigurationOffice365 } from './office365/office365'; import { getCore } from '../../../../../kibana-services'; +import { PromptAgentNeverConnected } from '../../../../../components/agents/prompts'; class WzConfigurationSwitch extends Component { constructor(props) { @@ -499,7 +499,7 @@ export default compose( ]), //TODO: this need cluster:read permission but manager/cluster is managed in WzConfigurationSwitch component withRenderIfOrWrapped( props => props.agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED, - WzAgentNeverConnectedPrompt, + PromptAgentNeverConnected, ), connect(mapStateToProps, mapDispatchToProps), )(WzConfigurationSwitch); diff --git a/plugins/main/public/controllers/overview/components/overview-actions/overview-actions.js b/plugins/main/public/controllers/overview/components/overview-actions/overview-actions.js index 6d7d04f1e6..644c2e71f0 100644 --- a/plugins/main/public/controllers/overview/components/overview-actions/overview-actions.js +++ b/plugins/main/public/controllers/overview/components/overview-actions/overview-actions.js @@ -12,21 +12,11 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { - showExploreAgentModal, + showExploreAgentModalGlobal, updateCurrentAgentData, } from '../../../../redux/actions/appStateActions'; -import { - EuiOverlayMask, - EuiOutsideClickDetector, - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiPopover, -} from '@elastic/eui'; import { WzButton } from '../../../../components/common/buttons'; import './agents-selector.scss'; -import { AgentSelectionTable } from './agents-selection-table'; import { AppState } from '../../../../react-services/app-state'; import { getDataPlugin } from '../../../../kibana-services'; import { getSettingDefaultValue } from '../../../../../common/services/settings'; @@ -44,7 +34,7 @@ class OverviewActions extends Component { async removeAgentsFilter(shouldUpdate = true) { await this.props.setAgent(false); const currentAppliedFilters = this.state.filterManager.filters; - const agentFilters = currentAppliedFilters.filter((x) => { + const agentFilters = currentAppliedFilters.filter(x => { return x.meta.key !== 'agent.id'; }); this.state.filterManager.setFilters(agentFilters); @@ -54,7 +44,8 @@ class OverviewActions extends Component { const { filterManager } = getDataPlugin().query; this.setState({ filterManager: filterManager }, () => { - if (this.props.initialFilter) this.agentTableSearch([this.props.initialFilter]); + if (this.props.initialFilter) + this.agentTableSearch([this.props.initialFilter]); if (this.props.agent.id) this.agentTableSearch([this.props.agent.id]); }); } @@ -62,7 +53,10 @@ class OverviewActions extends Component { componentDidUpdate() { if (this.state.isAgent && !this.props.agent.id) { this.setState({ isAgent: false }); - } else if (this.props.agent.id && this.state.isAgent !== this.props.agent.id) { + } else if ( + this.props.agent.id && + this.state.isAgent !== this.props.agent.id + ) { this.setState({ isAgent: this.props.agent.id }); } } @@ -82,7 +76,7 @@ class OverviewActions extends Component { closeAgentModal() { this.setState({ isAgentModalVisible: false }); - this.props.showExploreAgentModal(false); + this.props.showExploreAgentModalGlobal(false); } showAgentModal() { @@ -99,7 +93,7 @@ class OverviewActions extends Component { if (agentIdList && agentIdList.length) { if (agentIdList.length === 1) { const currentAppliedFilters = this.state.filterManager.filters; - const agentFilters = currentAppliedFilters.filter((x) => { + const agentFilters = currentAppliedFilters.filter(x => { return x.meta.key !== 'agent.id'; }); const filter = { @@ -110,7 +104,8 @@ class OverviewActions extends Component { negate: false, params: { query: agentIdList[0] }, type: 'phrase', - index: AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), + index: + AppState.getCurrentPattern() || getSettingDefaultValue('pattern'), }, query: { match: { @@ -135,7 +130,9 @@ class OverviewActions extends Component { let selectedAgentsObject = {}; for ( var i = 0; - this.state.isAgent && this.state.isAgent.length && i < this.state.isAgent.length; + this.state.isAgent && + this.state.isAgent.length && + i < this.state.isAgent.length; ++i ) selectedAgentsObject[this.state.isAgent[i]] = true; @@ -143,61 +140,34 @@ class OverviewActions extends Component { } render() { - let modal; - - if (this.state.isAgentModalVisible || this.props.state.showExploreAgentModal) { - modal = ( - - this.closeAgentModal()}> - this.closeAgentModal()} - initialFocus="[name=popswitch]" - > - - Explore agent - - - - this.agentTableSearch(agentsIdList)} - removeAgentsFilter={(shouldUpdate) => this.removeAgentsFilter(shouldUpdate)} - selectedAgents={this.getSelectedAgents()} - > - - - - - ); - } - const thereAgentSelected = (this.props.agent || {}).id; const avaliableForAgent = - this.props.module.availableFor && this.props.module.availableFor.includes('agent'); + 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" + aria-label='Unpin agent' /> ); } buttonExploreAgent = ( this.showAgentModal()} + style={ + thereAgentSelected + ? { background: 'rgba(0, 107, 180, 0.1)' } + : undefined + } + iconType='watchesApp' + onClick={() => this.props.showExploreAgentModalGlobal(true)} > - {thereAgentSelected ? `${this.props.agent.name} (${this.props.agent.id})` : 'Explore agent'} + {thereAgentSelected + ? `${this.props.agent.name} (${this.props.agent.id})` + : 'Explore agent'} ); @@ -219,22 +195,22 @@ class OverviewActions extends Component {
{buttonExploreAgent} {thereAgentSelected && buttonUnpinAgent} - {modal}
); } } -const mapStateToProps = (state) => { +const mapStateToProps = state => { return { state: state.appStateReducers, agent: state.appStateReducers.currentAgentData, }; }; -const mapDispatchToProps = (dispatch) => ({ - updateCurrentAgentData: (agent) => dispatch(updateCurrentAgentData(agent)), - showExploreAgentModal: (data) => dispatch(showExploreAgentModal(data)), +const mapDispatchToProps = dispatch => ({ + updateCurrentAgentData: agent => dispatch(updateCurrentAgentData(agent)), + showExploreAgentModalGlobal: data => + dispatch(showExploreAgentModalGlobal(data)), }); export default connect(mapStateToProps, mapDispatchToProps)(OverviewActions); diff --git a/plugins/main/public/redux/actions/appStateActions.js b/plugins/main/public/redux/actions/appStateActions.js index c73a785c00..37c6a93b0e 100644 --- a/plugins/main/public/redux/actions/appStateActions.js +++ b/plugins/main/public/redux/actions/appStateActions.js @@ -76,17 +76,6 @@ export const updateCurrentAgentData = data => { }; }; -/** - * Updates showExploreAgentModal in the appState store - * @param shouldShow - */ -export const showExploreAgentModal = shouldShow => { - return { - type: 'SHOW_EXPLORE_AGENT_MODAL', - showExploreAgentModal: shouldShow, - }; -}; - /** * Updates showExploreAgentModalGlobal in the appState store * @param shouldShow diff --git a/plugins/main/public/redux/reducers/appStateReducers.js b/plugins/main/public/redux/reducers/appStateReducers.js index 6f0c85ffc3..69066c38c3 100644 --- a/plugins/main/public/redux/reducers/appStateReducers.js +++ b/plugins/main/public/redux/reducers/appStateReducers.js @@ -19,7 +19,6 @@ const initialState = { currentAgentData: JSON.parse( window.sessionStorage.getItem('wz-shared-selected-agent') || '{}', ), - showExploreAgentModal: false, showExploreAgentModalGlobal: false, userPermissions: false, userRoles: [], @@ -76,13 +75,6 @@ const appStateReducers = (state = initialState, action) => { }; } - if (action.type === 'SHOW_EXPLORE_AGENT_MODAL') { - return { - ...state, - showExploreAgentModal: action.showExploreAgentModal, - }; - } - if (action.type === 'SHOW_EXPLORE_AGENT_MODAL_GLOBAL') { return { ...state, diff --git a/plugins/main/public/services/common-data.js b/plugins/main/public/services/common-data.js index 3a4a702c82..285285be5a 100644 --- a/plugins/main/public/services/common-data.js +++ b/plugins/main/public/services/common-data.js @@ -399,8 +399,9 @@ export class CommonData { return this.$location.search().agent; } else { this.shareAgent.deleteAgent(); - this.$location.search('agent', globalAgent.id); - return globalAgent.id; + const agentId = globalAgent?.id || null; + agentId && this.$location.search('agent', agentId); + return agentId; } } } diff --git a/plugins/main/public/templates/agents/dashboards.html b/plugins/main/public/templates/agents/dashboards.html index 4766c6df1e..33fe5c8c5e 100644 --- a/plugins/main/public/templates/agents/dashboards.html +++ b/plugins/main/public/templates/agents/dashboards.html @@ -4,35 +4,17 @@ ng-show="load && tab !== 'configuration' && tab !== 'sca' && tabView === 'panels'" ng-style="tab === 'welcome' && {'padding':16}" > - +
-
-
-
- -
- - Could not fetch data for this agent. - Reason: {{emptyAgent}} -
-
- Try again -
-
-
-
-
-
-
+
+
-
+
-
+
@@ -86,7 +72,9 @@ ng-if="reportBusy && reportStatus && showModuleDashboard" class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--justifyContentSpaceAround euiFlexGroup--directionRow euiFlexGroup--responsive" > -
+
@@ -127,13 +115,19 @@
-
- +
+
-
- +
+