diff --git a/public/components/common/restart-cluster-manager-callout.tsx b/public/components/common/restart-cluster-manager-callout.tsx new file mode 100644 index 0000000000..cf06d7aec8 --- /dev/null +++ b/public/components/common/restart-cluster-manager-callout.tsx @@ -0,0 +1,130 @@ +/* + * Wazuh app - React component for registering agents. + * 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'; + + // Eui components +import { + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiOverlayMask, + EuiConfirmModal, + EuiText, + EuiIcon +} from '@elastic/eui'; + +import { getToasts } from '../../kibana-services'; +import { updateWazuhNotReadyYet } from '../../redux/actions/appStateActions'; +import { clusterReq, restartClusterOrManager } from '../../controllers/management/components/management/configuration/utils/wz-fetch'; +import { connect } from 'react-redux'; + +interface IWzRestartClusterManagerCalloutProps{ + updateWazuhNotReadyYet: (wazuhNotReadyYet) => void + onRestarted: () => void + onRestartedError: () => void +}; + +interface IWzRestartClusterManagerCalloutState{ + warningRestarting: boolean + warningRestartModalVisible: boolean + isCluster: boolean +}; + +class WzRestartClusterManagerCallout extends Component{ + constructor(props){ + super(props); + this.state = { + warningRestarting: false, + warningRestartModalVisible: false, + isCluster: false + }; + } + toggleWarningRestartModalVisible(){ + this.setState({ warningRestartModalVisible: !this.state.warningRestartModalVisible }) + } + showToast(color, title, text = '', time = 3000){ + getToasts().add({ + color, + title, + text, + toastLifeTimeMs: time + }); + } + restartClusterOrManager = async () => { + try{ + this.setState({ warningRestarting: true, warningRestartModalVisible: false}); + const data = await restartClusterOrManager(this.props.updateWazuhNotReadyYet); + this.props.onRestarted(); + this.showToast('success', `${data.restarted} was restarted`); + }catch(error){ + this.setState({ warningRestarting: false }); + this.props.updateWazuhNotReadyYet(false); + this.props.onRestartedError(); + this.showToast('danger', 'Error', error.message || error ); + } + }; + async componentDidMount(){ + try{ + const clusterStatus = await clusterReq(); + this.setState( { isCluster: clusterStatus.data.data.enabled === 'yes' && clusterStatus.data.data.running === 'yes' }); + }catch(error){} + } + render(){ + const { warningRestarting, warningRestartModalVisible } = this.state; + return ( + + {!warningRestarting && ( + + + + + + Changes will not take effect until a restart is performed. + + + + this.toggleWarningRestartModalVisible()} + > + {'Restart'} + + + + + )} + {warningRestartModalVisible && ( + + this.toggleWarningRestartModalVisible()} + onConfirm={() => this.restartClusterOrManager()} + cancelButtonText="Cancel" + confirmButtonText="Confirm" + defaultFocusedButton="cancel" + > + + )} + + ) + } +} + +const mapDispatchToProps = dispatch => { + return { + updateWazuhNotReadyYet: wazuhNotReadyYet => dispatch(updateWazuhNotReadyYet(wazuhNotReadyYet)) + } +}; + +export default connect(null, mapDispatchToProps)(WzRestartClusterManagerCallout) \ No newline at end of file diff --git a/public/components/common/restart-modal/restart-modal.tsx b/public/components/common/restart-modal/restart-modal.tsx deleted file mode 100644 index 6287f4e4f3..0000000000 --- a/public/components/common/restart-modal/restart-modal.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiEmptyPrompt, - EuiEmptyPromptProps, - EuiFlexGroup, - EuiFlexItem, - EuiOverlayMask, - EuiProgress, -} from '@elastic/eui'; -import { getHttp } from '../../../kibana-services'; -import { WazuhConfig } from '../../../react-services/wazuh-config'; -import { RestartHandler } from '../../../react-services/wz-restart'; -import { getAssetURL, getThemeAssetURL } from '../../../utils/assets'; -import { useDispatch, useSelector } from 'react-redux'; -import { - updateRestartStatus, - updateRestartAttempt, - updateSyncCheckAttempt, - updateUnsynchronizedNodes, -} from '../../../redux/actions/appStateActions'; -import { UI_LOGGER_LEVELS } from '../../../../common/constants'; -import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; -import { getErrorOrchestrator } from '../../../react-services/common-services'; - -/** - * The restart modal to show feedback to the user. - * @param props component's props - * @returns components's body - */ -export const RestartModal = (props: { useDelay?: boolean, showWarningRestart? }) => { - // Component props - const { useDelay = false } = props; - - // Use default HEALTHCHECK_DELAY - const [timeToHC, setTimeToHC] = useState(RestartHandler.HEALTHCHECK_DELAY / 1000); - - // Use default SYNC_DELAY - const [syncDelay, setSyncDelay] = useState(RestartHandler.SYNC_DELAY / 1000); - - // Restart polling counter - const restartAttempt = useSelector((state) => state.appStateReducers.restartAttempt); - - // Cluster nodes that did not synced - const unsyncedNodes = useSelector((state) => state.appStateReducers.unsynchronizedNodes); - - // Sync polling counter - const syncPollingAttempt = useSelector((state) => state.appStateReducers.syncCheckAttempt); - - // Current status of the restarting process - const restartStatus = useSelector((state) => state.appStateReducers.restartStatus); - - // Max attempts = MAX_SYNC_POLLING_ATTEMPTS + MAX_RESTART_POLLING_ATTEMPTS - const maxAttempts = useDelay - ? RestartHandler.MAX_RESTART_POLLING_ATTEMPTS + RestartHandler.MAX_SYNC_POLLING_ATTEMPTS - : RestartHandler.MAX_RESTART_POLLING_ATTEMPTS; - - // Current attempt - const attempts = useDelay ? restartAttempt + syncPollingAttempt : restartAttempt; - - // Load Wazuh logo - const wzConfig = new WazuhConfig().getConfig(); - const logotypeURL = getHttp().basePath.prepend( - wzConfig['customization.logo.sidebar'] - ? getAssetURL(wzConfig['customization.logo.sidebar']) - : getThemeAssetURL('icon.svg') - ); - - // Apply SYNC_DELAY if useDelay prop is enabled. - useEffect(() => { - if (useDelay) { - countdown(syncDelay, setSyncDelay); - } - }, []); - - // Apply HEALTHCHECK_DELAY when the restart has failed - useEffect(() => { - restartStatus === RestartHandler.RESTART_STATES.RESTART_ERROR && - countdown(timeToHC, setTimeToHC); - - restartStatus === RestartHandler.RESTART_STATES.RESTARTED_INFO && - restartedTimeout(); - }, [restartStatus]); - - // TODO - const countdown = (time: number, setState) => { - let countDown = time; - - const interval = setInterval(() => { - setState(countDown); - if (countDown === 0 && setState === setTimeToHC) { - clearInterval(interval); - RestartHandler.goToHealthcheck(); - } else if (countDown === 0) { - clearInterval(interval); - } - - countDown--; - }, 1000 /* 1 second */); - }; - - const restartedTimeout = () => { - setTimeout(() => { - dispatch(updateRestartStatus(RestartHandler.RESTART_STATES.RESTARTED)); - if(props.showWarningRestart){ - props.showWarningRestart() - } - }, RestartHandler.INFO_RESTART_SUCCESS_DELAY); - } - - // TODO review if importing these functions in wz-restart work. - const dispatch = useDispatch(); - const updateRedux = { - updateRestartAttempt: (restartAttempt) => dispatch(updateRestartAttempt(restartAttempt)), - updateSyncCheckAttempt: (syncCheckAttempt) => - dispatch(updateSyncCheckAttempt(syncCheckAttempt)), - updateUnsynchronizedNodes: (unsynchronizedNodes) => - dispatch(updateUnsynchronizedNodes(unsynchronizedNodes)), - updateRestartStatus: (restartStatus) => dispatch(updateRestartStatus(restartStatus)), - }; - - const forceRestart = async () => { - try{ - await RestartHandler.restartWazuh(updateRedux); - }catch(error){ - const options = { - context: `${RestartModal.name}.forceRestart`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - getErrorOrchestrator().handleError(options); - } - }; - - - const abort = () => { - dispatch(updateRestartStatus(RestartHandler.RESTART_STATES.RESTARTED)); - dispatch(updateUnsynchronizedNodes([])); - dispatch(updateRestartAttempt(0)); - dispatch(updateSyncCheckAttempt(0)); - } - - // Build the modal depending on the restart state. - let emptyPromptProps: Partial; - switch (restartStatus) { - default: - case RestartHandler.RESTART_STATES.RESTARTED_INFO: - emptyPromptProps = { - title: ( - <> - -

Wazuh restarted

- - ), - body: ( - <> - - - ), - }; - break; - - case RestartHandler.RESTART_STATES.RESTARTING: - emptyPromptProps = { - title: ( - <> - -

Restarting Wazuh

- - ), - body: ( - <> - - - ), - }; - break; - - case RestartHandler.RESTART_STATES.RESTART_ERROR: - emptyPromptProps = { - iconType: 'alert', - iconColor: 'danger', - title:

Unable to connect to Wazuh.

, - body:

There was an error restarting Wazuh. The Healthcheck will run automatically.

, - actions: ( - - Go to Healthcheck ({timeToHC} s) - - ), - }; - break; - - case RestartHandler.RESTART_STATES.SYNC_ERROR: - emptyPromptProps = { - iconType: 'alert', - iconColor: 'danger', - title:

Synchronization failed

, - body: ( -

- The nodes {unsyncedNodes.join(', ')} did not synchronize. Restarting Wazuh might set the - cluster into an inconsistent state. Close and try again later. -

- ), - actions: ( - - - - Force restart - - - - - Close - - - - ), - }; - break; - - case RestartHandler.RESTART_STATES.SYNCING: - emptyPromptProps = { - title: ( - <> - -

Synchronizing Wazuh

- - ), - body: ( - <> - - - ), - }; - break; - } - - // - return ( - -
- -
-
- ); -}; diff --git a/public/components/common/wz-restart-callout.tsx b/public/components/common/wz-restart-callout.tsx deleted file mode 100644 index 76fd2df5a5..0000000000 --- a/public/components/common/wz-restart-callout.tsx +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Wazuh app - React component for registering agents. - * 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'; - -// Eui components -import { - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiCallOut, - EuiOverlayMask, - EuiConfirmModal, - EuiText, - EuiIcon, -} from '@elastic/eui'; - -import { getToasts } from '../../kibana-services'; -import { updateRestartAttempt, updateSyncCheckAttempt, updateUnsynchronizedNodes, updateRestartStatus } from '../../redux/actions/appStateActions'; -import { RestartHandler } from '../../react-services/wz-restart'; -import { connect } from 'react-redux'; -import { RestartModal } from './restart-modal/restart-modal'; - -interface IWzRestartCalloutProps { - updateRestartAttempt: (restartAttempt) => void; - updateSyncCheckAttempt: (syncCheckAttempt) => void; - updateUnsynchronizedNodes: (unsynchronizedNodes) => void; - updateRestartStatus: (restartStatus) => void; - onRestarted: () => void; - onRestartedError: () => void; - restartStatus: string; -} - -interface IWzRestartCalloutState { - warningRestarting: boolean; - warningRestartModalVisible: boolean; - isCluster: boolean; - isRestarting: boolean; - timeoutRestarting: boolean; -} - -class WzRestartCallout extends Component { - constructor(props) { - super(props); - this.state = { - warningRestarting: false, - warningRestartModalVisible: false, - isCluster: false, - isRestarting: false, - timeoutRestarting: false, - }; - } - toggleWarningRestartModalVisible() { - this.setState({ - warningRestartModalVisible: !this.state.warningRestartModalVisible, - isRestarting: false, - }); - } - showToast(color, title, text = '', time = 3000) { - getToasts().add({ - color, - title, - text, - toastLifeTimeMs: time, - }); - } - restartWazuh = async () => { - try { - this.setState({ - warningRestarting: true, - warningRestartModalVisible: false, - isRestarting: true, - timeoutRestarting: true, - }); - const updateRedux = { - updateRestartAttempt: this.props.updateRestartAttempt, - updateSyncCheckAttempt: this.props.updateSyncCheckAttempt, - updateUnsynchronizedNodes: this.props.updateUnsynchronizedNodes, - updateRestartStatus: this.props.updateRestartStatus, - }; - await RestartHandler.restartWazuh(updateRedux, this.state.isCluster); - this.setState({ isRestarting: false }); - } catch (error) { - this.setState({ warningRestarting: false, isRestarting: false }); - this.props.onRestartedError(); - this.showToast('danger', 'Error', error.message || error); - } - }; - async componentDidMount() { - try { - const clusterStatus = await RestartHandler.clusterReq(); - this.setState({ - isCluster: - clusterStatus.data.data.enabled === 'yes' && clusterStatus.data.data.running === 'yes', - }); - } catch (error) {} - } - render() { - const { - warningRestarting, - warningRestartModalVisible, - isCluster, - timeoutRestarting, - } = this.state; - return ( - - {!warningRestarting && ( - - - - - - Changes will not take effect until a restart is performed. - - - - this.toggleWarningRestartModalVisible()} - > - {'Restart'} - - - - - )} - {warningRestartModalVisible && ( - - this.toggleWarningRestartModalVisible()} - onConfirm={() => { - isCluster - ? this.props.updateRestartStatus(RestartHandler.RESTART_STATES.SYNCING) - : this.props.updateRestartStatus(RestartHandler.RESTART_STATES.RESTARTING); - this.restartWazuh(); - }} - cancelButtonText="Cancel" - confirmButtonText="Confirm" - defaultFocusedButton="cancel" - > - - )} - {timeoutRestarting && - this.props.restartStatus !== RestartHandler.RESTART_STATES.RESTARTED && ( - this.props.onRestarted()} /> - )} - - ); - } -} - -const mapStateToProps = state => ({ - restartStatus: state.appStateReducers.restartStatus, -}); - -const mapDispatchToProps = (dispatch) => { - return { - updateRestartAttempt: (restartAttempt) => - dispatch(updateRestartAttempt(restartAttempt)), - updateSyncCheckAttempt: (syncCheckAttempt) => - dispatch(updateSyncCheckAttempt(syncCheckAttempt)), - updateUnsynchronizedNodes: (unsynchronizedNodes) => - dispatch(updateUnsynchronizedNodes(unsynchronizedNodes)), - updateRestartStatus: (restartStatus) => - dispatch(updateRestartStatus(restartStatus)), - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(WzRestartCallout); diff --git a/public/controllers/management/components/management/configuration/edit-configuration/edit-configuration.js b/public/controllers/management/components/management/configuration/edit-configuration/edit-configuration.js index dd740995d9..6bd807fe20 100644 --- a/public/controllers/management/components/management/configuration/edit-configuration/edit-configuration.js +++ b/public/controllers/management/components/management/configuration/edit-configuration/edit-configuration.js @@ -30,19 +30,19 @@ import WzConfigurationPath from '../util-components/configuration-path'; import WzRefreshClusterInfoButton from '../util-components/refresh-cluster-info-button'; import { WzButtonPermissions } from '../../../../../../components/common/permissions/button'; import withLoading from '../util-hocs/loading'; -import { updateRestartStatus, updateRestartAttempt, updateSyncCheckAttempt, updateUnsynchronizedNodes } from '../../../../../../redux/actions/appStateActions'; +import { updateWazuhNotReadyYet } from '../../../../../../redux/actions/appStateActions'; import { updateClusterNodes, updateClusterNodeSelected, } from '../../../../../../redux/actions/configurationActions'; import { fetchFile, + restartNodeSelected, saveFileManager, saveFileCluster, clusterNodes, clusterReq, } from '../utils/wz-fetch'; -import { RestartHandler } from '../../../../../../react-services/wz-restart'; import { validateXML } from '../utils/xml'; import { getToasts } from '../../../../../..//kibana-services'; @@ -52,7 +52,6 @@ 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'; -import { RestartModal } from '../../../../../../components/common/restart-modal/restart-modal'; class WzEditConfiguration extends Component { constructor(props) { @@ -67,7 +66,6 @@ class WzEditConfiguration extends Component { hasChanges: false, infoChangesAfterRestart: false, disableSaveRestartButtons: false, - timeoutRestarting: false }; } @@ -159,19 +157,34 @@ class WzEditConfiguration extends Component { async confirmRestart() { try { - this.setState({ restarting: true, saving: true, infoChangesAfterRestart: false, timeoutRestarting:true }); - const updateRedux = { - updateRestartStatus: this.props.updateRestartStatus, - updateSyncCheckAttempt: this.props.updateSyncCheckAttempt, - updateUnsynchronizedNodes: this.props.updateUnsynchronizedNodes, - updateRestartAttempt: this.props.updateRestartAttempt, - } - await RestartHandler.restartSelectedNode( - this.props.clusterNodeSelected, updateRedux - ); + this.setState({ restarting: true, saving: true, infoChangesAfterRestart: false }); + await restartNodeSelected(this.props.clusterNodeSelected, this.props.updateWazuhNotReadyYet); + this.props.updateWazuhNotReadyYet(''); this.setState({ restart: false, saving: false, restarting: false }); await this.checkIfClusterOrManager(); + if (this.props.clusterNodes) { + this.addToast({ + title: ( + + +   + + Nodes could take some time to restart, it may be necessary to perform a refresh to + see them all. + + + ), + color: 'success', + }); + } + if(!this.props.clusterNodeSelected){ + this.addToast({ + title: 'Manager was restarted', + color: 'success', + }); + } } catch (error) { + this.props.updateWazuhNotReadyYet(''); this.setState({ restart: false, saving: false, restarting: false }); const options = { context: `${WzEditConfiguration.name}.confirmRestart`, @@ -187,7 +200,6 @@ class WzEditConfiguration extends Component { } } - async checkIfClusterOrManager() { try { // in case which enable/disable cluster configuration, update Redux Store @@ -222,7 +234,7 @@ class WzEditConfiguration extends Component { } } render() { - const { restart, restarting, saving, editorValue, disableSaveRestartButtons, timeoutRestarting } = this.state; + const { restart, restarting, saving, editorValue, disableSaveRestartButtons } = this.state; const { clusterNodeSelected, agent } = this.props; const xmlError = editorValue && validateXML(editorValue); return ( @@ -300,10 +312,6 @@ class WzEditConfiguration extends Component { /> )} - { - timeoutRestarting && this.props.restartStatus !== RestartHandler.RESTART_STATES.RESTARTED && ( - - )} ); } @@ -313,22 +321,18 @@ const mapStateToProps = (state) => ({ wazuhNotReadyYet: state.appStateReducers.wazuhNotReadyYet, clusterNodes: state.configurationReducers.clusterNodes, clusterNodeSelected: state.configurationReducers.clusterNodeSelected, - restartStatus: state.appStateReducers.restartStatus, }); const mapDispatchToProps = (dispatch) => ({ updateClusterNodes: (clusterNodes) => dispatch(updateClusterNodes(clusterNodes)), updateClusterNodeSelected: (clusterNodeSelected) => dispatch(updateClusterNodeSelected(clusterNodeSelected)), - updateRestartAttempt: (value) => dispatch(updateRestartAttempt(value)), - updateRestartStatus: (value) => dispatch(updateRestartStatus(value)), - updateSyncCheckAttempt: (value) => dispatch(updateSyncCheckAttempt(value)), - updateUnsynchronizedNodes: (value) => dispatch(updateUnsynchronizedNodes(value)), + updateWazuhNotReadyYet: (value) => dispatch(updateWazuhNotReadyYet(value)), }); WzEditConfiguration.propTypes = { wazuhNotReadyYet: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), - updateRestartAttempt: PropTypes.func, + updateWazuhNotReadyYet: PropTypes.func, }; export default connect(mapStateToProps, mapDispatchToProps)(WzEditConfiguration); diff --git a/public/controllers/management/components/management/configuration/utils/wz-fetch.js b/public/controllers/management/components/management/configuration/utils/wz-fetch.js index 686faebc2b..e13bd3d9f3 100644 --- a/public/controllers/management/components/management/configuration/utils/wz-fetch.js +++ b/public/controllers/management/components/management/configuration/utils/wz-fetch.js @@ -12,6 +12,7 @@ import { WzRequest } from '../../../../../../react-services/wz-request'; import { replaceIllegalXML } from './xml'; +import { getToasts } from '../../../../../../kibana-services'; import { delayAsPromise } from '../../../../../../../common/utils'; /** @@ -185,7 +186,6 @@ export const checkDaemons = async (isCluster) => { }; /** - * @deprecated * Make ping to Wazuh API * @param updateWazuhNotReadyYet * @param {boolean} isCluster @@ -262,6 +262,106 @@ export const fetchFile = async selectedNode => { } }; +/** + * Restart a node or manager + * @param {} selectedNode Cluster Node + * @param updateWazuhNotReadyYet + */ +export const restartNodeSelected = async (selectedNode, updateWazuhNotReadyYet) => { + try { + const clusterStatus = (((await clusterReq()) || {}).data || {}).data || {}; + const isCluster = clusterStatus.enabled === 'yes' && clusterStatus.running === 'yes'; + // Dispatch a Redux action + updateWazuhNotReadyYet(`Restarting ${isCluster ? selectedNode : 'Manager'}, please wait.`); //FIXME: if it enables/disables cluster, this will show Manager instead node name + isCluster ? await restartNode(selectedNode) : await restartManager(); + return await makePing(updateWazuhNotReadyYet, isCluster); + } catch (error) { + throw error; + } +}; + +/** + * Restart manager (single-node API call) + * @returns {object|Promise} + */ +export const restartManager = async () => { + try { + const validationError = await WzRequest.apiReq( + 'GET', + `/manager/configuration/validation`, {} + ); + const isOk = validationError.status === 'OK'; + if (!isOk && validationError.detail) { + const str = validationError.detail; + throw new Error(str); + } + const result = await WzRequest.apiReq('PUT', `/manager/restart`, {}); + return result; + } catch (error) { + throw error; + } +}; + +/** + * Restart cluster + * @returns {object|Promise} + */ +export const restartCluster = async () => { + try { + const validationError = await WzRequest.apiReq( + 'GET', + `/cluster/configuration/validation`, {} + ); + + const isOk = validationError.status === 'OK'; + if (!isOk && validationError.detail) { + const str = validationError.detail; + throw new Error(str); + } + // this.performClusterRestart(); // TODO: convert AngularJS to React + await WzRequest.apiReq('PUT', `/cluster/restart`, { + delay: 15000 + }); + // this.$rootScope.$broadcast('removeRestarting', {}); TODO: isRestarting: false? + return { + data: { + data: 'Restarting cluster' + } + }; + } catch (error) { + throw error; + } +}; + +/** + * Restart a cluster node + * @returns {object|Promise} + */ +export const restartNode = async node => { + try { + const node_param = node && typeof node == 'string' ? `?nodes_list=${node}` : ''; + + const validationError = await WzRequest.apiReq( + 'GET', + `/cluster/configuration/validation`, {} + ); + + const isOk = validationError.status === 200; + if (!isOk && validationError.detail) { + const str = validationError.detail; + throw new Error(str); + } + const result = await WzRequest.apiReq( + 'PUT', + `/cluster/restart${node_param}`, {delay: 15000} + ); + + return result; + } catch (error) { + throw error; + } +}; + export const saveConfiguration = async (selectedNode, xml) => { try { const clusterStatus = (((await clusterReq()) || {}).data || {}).data || {}; @@ -413,4 +513,28 @@ export const checkCurrentSecurityPlatform = async () => { } catch (error) { throw error; } -}; \ No newline at end of file +}; + +/** + * Restart cluster or Manager + */ +export const restartClusterOrManager = async (updateWazuhNotReadyYet) => { + try { + const clusterStatus = (((await clusterReq()) || {}).data || {}).data || {}; + const isCluster = clusterStatus.enabled === 'yes' && clusterStatus.running === 'yes'; + getToasts().add({ + color: 'success', + title: isCluster + ? 'Restarting cluster, it will take up to 30 seconds.' + : 'The manager is being restarted', + toastLifeTimeMs: 3000, + }); + isCluster ? await restartCluster() : await restartManager(); + // Dispatch a Redux action + updateWazuhNotReadyYet(`Restarting ${isCluster ? 'Cluster' : 'Manager'}, please wait.`); + await makePing(updateWazuhNotReadyYet, isCluster); + return { restarted: isCluster ? 'Cluster' : 'Manager' }; + } catch (error) { + throw error; + } +}; diff --git a/public/controllers/management/components/management/ruleset/actions-buttons.js b/public/controllers/management/components/management/ruleset/actions-buttons.js index d9d87dc056..515496f6b9 100644 --- a/public/controllers/management/components/management/ruleset/actions-buttons.js +++ b/public/controllers/management/components/management/ruleset/actions-buttons.js @@ -26,7 +26,7 @@ import { import exportCsv from '../../../../../react-services/wz-csv'; import { UploadFiles } from '../../upload-files'; import columns from './utils/columns'; -import { resourceDictionary, RulesetHandler } from './utils/ruleset-handler'; +import { resourceDictionary, RulesetHandler, RulesetResources } from './utils/ruleset-handler'; import { WzButtonPermissions } from '../../../../../components/common/permissions/button'; import { connect } from 'react-redux'; diff --git a/public/controllers/management/components/management/ruleset/list-editor.js b/public/controllers/management/components/management/ruleset/list-editor.js index abf701f483..b91b4e6997 100644 --- a/public/controllers/management/components/management/ruleset/list-editor.js +++ b/public/controllers/management/components/management/ruleset/list-editor.js @@ -18,8 +18,10 @@ import { EuiTitle, EuiToolTip, EuiButtonIcon, + EuiButton, EuiText, EuiButtonEmpty, + EuiPopover, EuiFieldText, EuiSpacer, EuiPanel, @@ -36,7 +38,7 @@ import { getToasts } from '../../../../../kibana-services'; import exportCsv from '../../../../../react-services/wz-csv'; import { updateWazuhNotReadyYet } from '../../../../../redux/actions/appStateActions'; -import WzRestartCallout from '../../../../../components/common/wz-restart-callout'; +import WzRestartClusterManagerCallout from '../../../../../components/common/restart-cluster-manager-callout'; import { WzButtonPermissions } from '../../../../../components/common/permissions/button'; import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; @@ -600,7 +602,7 @@ class WzListEditor extends Component { {this.state.showWarningRestart && ( - this.setState({ showWarningRestart: false })} onRestartedError={() => this.setState({ showWarningRestart: true })} /> diff --git a/public/controllers/management/components/management/ruleset/ruleset-editor.js b/public/controllers/management/components/management/ruleset/ruleset-editor.js index adff07289c..73046b2d84 100644 --- a/public/controllers/management/components/management/ruleset/ruleset-editor.js +++ b/public/controllers/management/components/management/ruleset/ruleset-editor.js @@ -39,7 +39,7 @@ import validateConfigAfterSent from './utils/valid-configuration'; import { getToasts } from '../../../../../kibana-services'; import { updateWazuhNotReadyYet } from '../../../../../redux/actions/appStateActions'; -import WzRestartCallout from '../../../../../components/common/wz-restart-callout'; +import WzRestartClusterManagerCallout from '../../../../../components/common/restart-cluster-manager-callout'; import { validateXML } from '../configuration/utils/xml'; import { WzButtonPermissions } from '../../../../../components/common/permissions/button'; import 'brace/theme/textmate'; @@ -353,7 +353,7 @@ class WzRulesetEditor extends Component { {this.state.showWarningRestart && ( - this.setState({ showWarningRestart: false })} onRestartedError={() => this.setState({ showWarningRestart: true })} /> diff --git a/public/controllers/management/components/management/ruleset/ruleset-overview.js b/public/controllers/management/components/management/ruleset/ruleset-overview.js index 0bedd14fb6..d6d31a2924 100644 --- a/public/controllers/management/components/management/ruleset/ruleset-overview.js +++ b/public/controllers/management/components/management/ruleset/ruleset-overview.js @@ -19,7 +19,7 @@ import WzRulesetSearchBar from './ruleset-search-bar'; import WzRulesetActionButtons from './actions-buttons'; import './ruleset-overview.scss'; import { withUserAuthorizationPrompt, withGlobalBreadcrumb } from '../../../../../components/common/hocs'; -import WzRestartCallout from '../../../../../components/common/wz-restart-callout'; +import WzRestartClusterManagerCallout from '../../../../../components/common/restart-cluster-manager-callout'; import { compose } from 'redux'; import { resourceDictionary } from './utils/ruleset-handler'; @@ -68,7 +68,7 @@ class WzRulesetOverview extends Component { {this.state.showWarningRestart && ( <> - this.updateRestartManagers(false)} onRestartedError={() => this.updateRestartManagers(true)} /> diff --git a/public/controllers/management/components/management/status/actions-buttons-main.js b/public/controllers/management/components/management/status/actions-buttons-main.js index 94f496d18a..ce95c92052 100644 --- a/public/controllers/management/components/management/status/actions-buttons-main.js +++ b/public/controllers/management/components/management/status/actions-buttons-main.js @@ -13,6 +13,7 @@ import React, { Component, Fragment } from 'react'; // Eui components import { EuiFlexItem, + EuiButtonEmpty, EuiSelect, EuiOverlayMask, EuiConfirmModal @@ -36,9 +37,6 @@ import { WzButtonPermissions } from '../../../../../components/common/permission import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; import { getErrorOrchestrator } from '../../../../../react-services/common-services'; -import { RestartHandler } from '../../../../../react-services/wz-restart'; -import { updateRestartAttempt, updateRestartStatus } from '../../../../../redux/actions/appStateActions'; -import { RestartModal } from '../../../../../components/common/restart-modal/restart-modal' class WzStatusActionButtons extends Component { _isMounted = false; @@ -50,8 +48,7 @@ class WzStatusActionButtons extends Component { this.statusHandler = StatusHandler; this.state = { isModalVisible: false, - isRestarting: false, - timeoutRestarting: false, + isRestarting: false }; } @@ -59,29 +56,60 @@ class WzStatusActionButtons extends Component { this._isMounted = true; } + componentDidUpdate() {} + componentWillUnmount() { this._isMounted = false; } /** - * Restart cluster or manager + * Restart cluster */ - async restartWazuh() { - this.setState({ isRestarting: true, timeoutRestarting: true }); + async restartCluster() { + this.setState({ isRestarting: true }); try { - const updateRedux = {updateRestartAttempt: this.props.updateRestartAttempt , updateRestartStatus: this.props.updateRestartStatus}; - await RestartHandler.restartWazuh(updateRedux); + const result = await this.statusHandler.restartCluster(); this.setState({ isRestarting: false }); + this.showToast( + 'success', + 'Restarting cluster, it will take up to 30 seconds.', + 3000 + ); } catch (error) { this.setState({ isRestarting: false }); const options = { - context: `${WzStatusActionButtons.name}.restartWazuh`, + context: `${WzStatusActionButtons.name}.restartCluster`, level: UI_LOGGER_LEVELS.ERROR, severity: UI_ERROR_SEVERITIES.BUSINESS, error: { error: error, message: error.message || error, - title: `${error.name}: Error restarting Wazuh}`, + title: `${error.name}: Error restarting cluster`, + }, + }; + getErrorOrchestrator().handleError(options); + } + } + + /** + * Restart manager + */ + async restartManager() { + this.setState({ isRestarting: true }); + try { + await this.statusHandler.restartManager(); + this.setState({ isRestarting: false }); + this.showToast('success', 'Restarting manager.', 3000); + } catch (error) { + this.setState({ isRestarting: false }); + const options = { + context: `${WzStatusActionButtons.name}.restartManager`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + error: { + error: error, + message: error.message || error, + title: `${error.name}: Error restarting manager`, }, }; getErrorOrchestrator().handleError(options); @@ -186,14 +214,9 @@ class WzStatusActionButtons extends Component { listNodes, selectedNode, clusterEnabled, + isRestarting } = this.props.state; - const { - isRestarting, - isModalVisible, - timeoutRestarting, - } = this.state - let options = this.transforToOptions(listNodes); // Select node @@ -203,7 +226,7 @@ class WzStatusActionButtons extends Component { options={options} value={selectedNode} onChange={this.changeNode} - disabled={isLoading || isRestarting} + disabled={isLoading || this.state.isRestarting} aria-label="Select node" /> ); @@ -216,15 +239,16 @@ class WzStatusActionButtons extends Component { iconType="refresh" onClick={async () => this.setState({ isModalVisible: true })} isDisabled={isLoading} - isLoading={isRestarting} + isLoading={this.state.isRestarting} > - Restart {clusterEnabled ? 'cluster' : 'manager'} + {clusterEnabled && 'Restart cluster'} + {!clusterEnabled && 'Restart manager'} ); let modal; - if (isModalVisible) { + if (this.state.isModalVisible) { modal = ( { - this.props.updateRestartStatus(RestartHandler.RESTART_STATES.RESTARTING) - this.restartWazuh() + if (clusterEnabled) { + this.restartCluster(); + } else { + this.restartManager(); + } this.setState({ isModalVisible: false }); }} cancelButtonText="Cancel" @@ -247,20 +274,13 @@ class WzStatusActionButtons extends Component { ); } - let restarting - - if (timeoutRestarting && this.props.restartStatus !== RestartHandler.RESTART_STATES.RESTARTED) { - restarting = ; - } - return ( {selectedNode !== null && ( - {restartButton} + {restartButton} )} {selectedNode && {selectNode}} {modal} - {restarting} ); } @@ -268,8 +288,7 @@ class WzStatusActionButtons extends Component { const mapStateToProps = state => { return { - state: state.statusReducers, - restartStatus: state.appStateReducers.restartStatus, + state: state.statusReducers }; }; @@ -280,9 +299,7 @@ const mapDispatchToProps = dispatch => { updateNodeInfo: nodeInfo => dispatch(updateNodeInfo(nodeInfo)), updateSelectedNode: selectedNode => dispatch(updateSelectedNode(selectedNode)), updateStats: stats => dispatch(updateStats(stats)), - updateAgentInfo: agentInfo => dispatch(updateAgentInfo(agentInfo)), - updateRestartAttempt: restartAttempt => dispatch(updateRestartAttempt(restartAttempt)), - updateRestartStatus: restartStatus => dispatch(updateRestartStatus(restartStatus)), + updateAgentInfo: agentInfo => dispatch(updateAgentInfo(agentInfo)) }; }; diff --git a/public/controllers/management/components/management/status/status-overview.js b/public/controllers/management/components/management/status/status-overview.js index d764785239..a2f20feb3d 100644 --- a/public/controllers/management/components/management/status/status-overview.js +++ b/public/controllers/management/components/management/status/status-overview.js @@ -20,6 +20,7 @@ import { EuiPage, EuiSpacer, EuiFlexGrid, + EuiButton, } from '@elastic/eui'; import { connect } from 'react-redux'; diff --git a/public/controllers/management/components/management/status/utils/status-handler.js b/public/controllers/management/components/management/status/utils/status-handler.js index ededecaad6..3b9344690f 100644 --- a/public/controllers/management/components/management/status/utils/status-handler.js +++ b/public/controllers/management/components/management/status/utils/status-handler.js @@ -138,4 +138,53 @@ export default class StatusHandler { throw error; } } + + /** + * Restart cluster + */ + static async restartCluster() { + try { + const validationError = await WzRequest.apiReq( + 'GET', + `/cluster/configuration/validation`, + {} + ); + + const data = ((validationError || {}).data || {}).data || {}; + const isOk = data.status === 'OK'; + if (!isOk && Array.isArray(data.details)) { + const str = data.details.join(); + throw new Error(str); + } + await WzRequest.apiReq('PUT', `/cluster/restart`, { delay: 15000 }); + return { data: { data: 'Restarting cluster' } }; + } catch (error) { + throw error; + } + } + + /** + * Restart manager (single-node API call) + */ + static async restartManager() { + try { + const validationError = await WzRequest.apiReq( + 'GET', + `/manager/configuration/validation`, + {} + ); + + const data = ((validationError || {}).data || {}).data || {}; + const isOk = data.status === 'OK'; + if (!isOk && Array.isArray(data.details)) { + const str = data.details.join(); + throw new Error(str); + } + + const result = await WzRequest.apiReq('PUT', `/manager/restart`, {}); + return result; + } catch (error) { + throw error; + } + } } diff --git a/public/controllers/management/management.js b/public/controllers/management/management.js index 42432383d7..3629b75f10 100644 --- a/public/controllers/management/management.js +++ b/public/controllers/management/management.js @@ -30,12 +30,13 @@ export class ManagementController { * @param {*} $scope * @param {*} $location */ - constructor($scope, $rootScope, $location, errorHandler, $interval) { + constructor($scope, $rootScope, $location, configHandler, errorHandler, $interval) { this.$scope = $scope; this.$rootScope = $rootScope; this.$location = $location; this.shareAgent = new ShareAgent(); this.wazuhConfig = new WazuhConfig(); + this.configHandler = configHandler; this.errorHandler = errorHandler; this.$interval = $interval; this.tab = 'welcome'; @@ -140,6 +141,11 @@ export class ManagementController { this.$scope.$applyAsync(); }); + this.$rootScope.$on('performRestart', (ev) => { + ev.stopPropagation(); + this.clusterInfo.status === 'enabled' ? this.restartCluster() : this.restartManager(); + }); + this.$rootScope.timeoutIsReady; this.$rootScope.$watch('resultState', () => { if (this.$rootScope.timeoutIsReady) { @@ -242,6 +248,59 @@ export class ManagementController { return item && Array.isArray(array) && array.includes(item); } + async restartManager() { + try { + if (this.isRestarting) return; + this.isRestarting = true; + await this.configHandler.restartManager(); + this.isRestarting = false; + this.$scope.$applyAsync(); + ErrorHandler.info('Restarting manager.'); + } catch (error) { + this.isRestarting = false; + this.$scope.$applyAsync(); + + const errorOptions = { + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + context: `${ManagementController.name}.restartManager`, + error: { + error: error, + message: error?.message || '', + title: 'Error restarting manager', + }, + }; + + getErrorOrchestrator().handleError(errorOptions); + } + } + + async restartCluster() { + try { + if (this.isRestarting) return; + this.isRestarting = true; + await this.configHandler.restartCluster(); + this.isRestarting = false; + this.$scope.$applyAsync(); + ErrorHandler.info('Restarting cluster, it will take up to 30 seconds.'); + } catch (error) { + this.isRestarting = false; + this.$scope.$applyAsync(); + const errorOptions = { + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + context: `${ManagementController.name}.restartCluster`, + error: { + error: error, + message: error?.message || '', + title: 'Error restarting cluster', + }, + }; + + getErrorOrchestrator().handleError(errorOptions); + } + } + setConfigTab(tab, nav = false) { this.globalConfigTab = tab; if (nav) { diff --git a/public/react-services/check-daemons-status.js b/public/react-services/check-daemons-status.js index 6d28c7d306..b2a3bc7b02 100644 --- a/public/react-services/check-daemons-status.js +++ b/public/react-services/check-daemons-status.js @@ -16,9 +16,6 @@ import { delayAsPromise } from '../../common/utils'; let busy = false; -/** - * @deprecated - */ export class CheckDaemonsStatus { static async makePing(tries = 10) { try { diff --git a/public/react-services/index.ts b/public/react-services/index.ts index c4e553db57..ba403f2533 100644 --- a/public/react-services/index.ts +++ b/public/react-services/index.ts @@ -22,5 +22,4 @@ export * from './wz-request'; export * from './wz-security-opendistro'; export * from './wz-security-xpack'; export * from './wz-user-permissions'; -export * from './query-config' -export * from './wz-restart' \ No newline at end of file +export * from './query-config' \ No newline at end of file diff --git a/public/react-services/wz-restart.js b/public/react-services/wz-restart.js deleted file mode 100644 index 74ba9775b7..0000000000 --- a/public/react-services/wz-restart.js +++ /dev/null @@ -1,305 +0,0 @@ -import { WzRequest } from './wz-request'; -import { delayAsPromise } from '../../common/utils'; -import { getHttp } from '../kibana-services'; - -/** - * Wazuh restart wizard. - * - * Controls the Wazuh restart process. - */ -export class RestartHandler { - static MAX_RESTART_POLLING_ATTEMPTS = 30; - static MAX_SYNC_POLLING_ATTEMPTS = 10; - static POLLING_DELAY = 2000; // milliseconds - static SYNC_DELAY = 20000; // milliseconds - static HEALTHCHECK_DELAY = 10000; // milliseconds - static INFO_RESTART_SUCCESS_DELAY = 500; // seconds - static RESTART_STATES = { - // TODO change to enum (requires TS) - RESTART_ERROR: 'restart_error', - SYNC_ERROR: 'sync_error', - RESTARTING: 'restarting', - SYNCING: 'syncing', - RESTARTED: 'restarted', - RESTARTED_INFO: 'restarted_info', - }; - - /** - * Get Cluster status from Wazuh API - * @returns {Promise} - */ - static async clusterReq() { - try { - return WzRequest.apiReq('GET', '/cluster/status', {}); - } catch (error) { - return Promise.reject(error); - } - } - - /** - * Check daemons status - * @param {Object} updateRedux - Redux update function - * @param {number} attempt - * @param {Boolean} isCluster - Is cluster or not - * @returns {object|Promise} - */ - static async checkDaemons(updateRedux, attempt, isCluster) { - try { - updateRedux.updateRestartAttempt(attempt); - - const response = await WzRequest.apiReq( - 'GET', - '/manager/status', - {}, - { checkCurrentApiIsUp: false } - ); - - const daemons = ((((response || {}).data || {}).data || {}).affected_items || [])[0] || {}; - - const wazuhdbExists = typeof daemons['wazuh-db'] !== 'undefined'; - const execd = daemons['wazuh-execd'] === 'running'; - const modulesd = daemons['wazuh-modulesd'] === 'running'; - const wazuhdb = wazuhdbExists ? daemons['wazuh-db'] === 'running' : true; - - let clusterd = true; - if (isCluster) { - clusterd = daemons['wazuh-clusterd'] === 'running'; - } - - const isValid = execd && modulesd && wazuhdb && (isCluster ? clusterd : true); - - if (isValid) { - updateRedux.updateRestartAttempt(0); - return isValid; - } else { - updateRedux.updateRestartAttempt(0); - console.warn('Wazuh not ready yet'); - return false; - } - } catch (error) { - throw error; - } - } - - /** - * Check sync status - * @param {Object} updateRedux - Redux update function - * @param {number} attempt - * @returns {object|Promise} - */ - static async checkSync(updateRedux, attempt) { - try { - const { updateSyncCheckAttempt, updateUnsynchronizedNodes } = updateRedux; - updateSyncCheckAttempt(attempt); - const response = await WzRequest.apiReq('GET', '/cluster/ruleset/synchronization', {}); - - if (response.data.error != 0){ - throw response.data.message - } - - const nodes = response.data.data.affected_items; - - const isSynced = nodes.every((node) => node.synced); - - if (!isSynced) { - const unsyncedNodes = nodes.flatMap((node) => (node.synced ? [] : node.name)); - updateUnsynchronizedNodes(unsyncedNodes); - throw new Error(`Nodes ${unsyncedNodes.join(', ')} are not synced`); - } - - updateSyncCheckAttempt(RestartHandler.MAX_SYNC_POLLING_ATTEMPTS); - - return isSynced; - } catch (error) { - throw error; - } - } - - /** - * Make ping to Wazuh API - * @param updateRedux - * @param breakCondition - * @param {boolean} isCluster - * @return {Promise} - */ - static async makePingSync(updateRedux, breakCondition, isCluster = true) { - try { - let isValid = false; - - const maxAttempts = - breakCondition === this.checkDaemons - ? this.MAX_RESTART_POLLING_ATTEMPTS - : this.MAX_SYNC_POLLING_ATTEMPTS; - - for (let attempt = 1; attempt <= maxAttempts && !isValid; attempt++) { - await delayAsPromise(this.POLLING_DELAY); - try { - isValid = await breakCondition(updateRedux, attempt, isCluster); - } catch (error) { - // console.error(error); - //The message in the console is disabled because the user will see the error message on the healthcheck page. - } - } - - if (!isValid) { - return isValid; - } - return Promise.resolve( - `Wazuh is ${breakCondition === this.checkDaemons ? 'restarted' : 'synced'}` - ); - } catch (error) { - return false; - } - } - - /** - * Healthcheck redirect - */ - static goToHealthcheck() { - window.location.href = getHttp().basePath.prepend('/app/wazuh#/health-check'); - } - - /** - * Restart manager (single-node API call) - * @param isCluster - Is cluster or not - * @returns {object|Promise} - */ - static async restart(isCluster) { - try { - const clusterOrManager = isCluster ? 'cluster' : 'manager'; - - const validationError = await WzRequest.apiReq( - 'GET', - `/${clusterOrManager}/configuration/validation`, - {} - ); - - const isOk = validationError.status === 'OK'; - if (!isOk && validationError.detail) { - const str = validationError.detail; - throw new Error(str); - } - - await WzRequest.apiReq('PUT', `/${clusterOrManager}/restart`, {}); - - return { - data: { - data: `Restarting ${clusterOrManager}`, - }, - }; - } catch (error) { - throw error; - } - } - - /** - * Restart a cluster node - * @param node - Node name - * @returns {object|Promise} - */ - static async restartNode(node) { - try { - const node_param = node && typeof node == 'string' ? `?nodes_list=${node}` : ''; - - const validationError = await WzRequest.apiReq( - 'GET', - `/cluster/configuration/validation`, - {} - ); - - const isOk = validationError.status === 200; - if (!isOk && validationError.detail) { - const str = validationError.detail; - throw new Error(str); - } - const result = await WzRequest.apiReq('PUT', `/cluster/restart${node_param}`, {}); - return result; - } catch (error) { - throw error; - } - } - - /** - * Restart a node or manager - * @param {} selectedNode Cluster Node - * @param updateRedux Redux update function - */ - static async restartSelectedNode(selectedNode, updateRedux) { - try { - // Dispatch a Redux action - updateRedux.updateRestartStatus(this.RESTART_STATES.RESTARTING); - const clusterStatus = (((await this.clusterReq()) || {}).data || {}).data || {}; - const isCluster = clusterStatus.enabled === 'yes' && clusterStatus.running === 'yes'; - isCluster ? await this.restartNode(selectedNode) : await this.restart(isCluster); - - const isRestarted = await this.makePingSync(updateRedux, this.checkDaemons, isCluster); - if (!isRestarted) { - updateRedux.updateRestartStatus(this.RESTART_STATES.RESTART_ERROR); - this.restartValues(updateRedux); - throw new Error('Not restarted'); - } - updateRedux.updateRestartStatus(this.RESTART_STATES.RESTARTED_INFO); - return { restarted: isCluster ? 'Cluster' : 'Manager' }; - } catch (error) { - RestartHandler.clearState(updateRedux, this.RESTART_STATES.RESTART_ERROR); - throw error; - } - } - - /** - * Restart cluster or Manager - * @param updateRedux Redux update function - * @param useDelay need to delay synchronization? - */ - static async restartWazuh(updateRedux, useDelay = false) { - try { - if (useDelay) { - updateRedux.updateRestartStatus(this.RESTART_STATES.SYNCING); - - const isSync = await this.makePingSync(updateRedux, this.checkSync); - if (!isSync) { - updateRedux.updateRestartStatus(this.RESTART_STATES.SYNC_ERROR); - this.restartValues(updateRedux); - throw new Error('Not synced'); - } - } - - updateRedux.updateRestartStatus(this.RESTART_STATES.RESTARTING); - - const clusterStatus = (((await this.clusterReq()) || {}).data || {}).data || {}; - const isCluster = clusterStatus.enabled === 'yes' && clusterStatus.running === 'yes'; - // Dispatch a Redux action - await this.restart(isCluster); - const isRestarted = await this.makePingSync(updateRedux, this.checkDaemons, isCluster); - - if (!isRestarted) { - updateRedux.updateRestartStatus(this.RESTART_STATES.RESTART_ERROR); - this.restartValues(updateRedux); - throw new Error('Not restarted'); - } - - updateRedux.updateRestartStatus(this.RESTART_STATES.RESTARTED_INFO); - this.restartValues(updateRedux); - - return { restarted: isCluster ? 'Cluster' : 'Manager' }; - } catch (error) { - const errorType = error === 'Not synced' ? this.RESTART_STATES.SYNC_ERROR : this.RESTART_STATES.RESTART_ERROR; - RestartHandler.clearState(updateRedux, errorType); - throw error; - } - } - - static clearState(updateRedux, errorType) { - updateRedux.updateRestartAttempt(0); - updateRedux.updateRestartStatus(errorType); - } - - /** - * Resets attempts to 0 in redux - * @param {*} updateRedux - */ - static restartValues(updateRedux) { - updateRedux.updateSyncCheckAttempt && updateRedux.updateSyncCheckAttempt(0); - updateRedux.updateRestartAttempt(0); - } -} diff --git a/public/redux/actions/appStateActions.js b/public/redux/actions/appStateActions.js index 1d3093a494..e26d7ed3b1 100644 --- a/public/redux/actions/appStateActions.js +++ b/public/redux/actions/appStateActions.js @@ -199,7 +199,7 @@ export const showFlyoutLogtest = (showFlyout) => { export const updateDockedLogtest = (dockedFlyout) => { return { type: 'UPDATE_DOCKED_LOGTEST', - dockedFlyoutLogtest: dockedFlyout + dockedFlyoutLogtest: dockedFlyout }; }; @@ -235,52 +235,4 @@ export const updateLogtestToken = (logtestToken) => { type: 'UPDATE_LOGTEST_TOKEN', logtestToken: logtestToken }; -}; - -/** - * Update restart attempt - * @param {Number} restartAttempt - * @returns - */ -export const updateRestartAttempt = (restartAttempt) => { - return { - type: 'UPDATE_RESTART_ATTEMPT', - restartAttempt - }; -}; - -/** - * Update sync check attempt - * @param {Number} syncCheckAttempt - * @returns - */ -export const updateSyncCheckAttempt = (syncCheckAttempt) => { - return { - type: 'UPDATE_SYNC_CHECK_ATTEMPT', - syncCheckAttempt - }; -} - -/** - * Update unsynchronized nodes - * @param {Array} unsynchronizedNodes - * @returns - */ -export const updateUnsynchronizedNodes = (unsynchronizedNodes) => { - return { - type: 'UPDATE_UNSYNCHRONIZED_NODES', - unsynchronizedNodes - }; -}; - -/** - * Update status of the restarting process - * @param {string} restartStatus - * @returns - */ -export const updateRestartStatus = (restartStatus) => { - return { - type: 'UPDATE_RESTART_STATUS', - restartStatus - }; }; \ No newline at end of file diff --git a/public/redux/reducers/appStateReducers.js b/public/redux/reducers/appStateReducers.js index f2135b6ec4..c69e603300 100644 --- a/public/redux/reducers/appStateReducers.js +++ b/public/redux/reducers/appStateReducers.js @@ -31,10 +31,6 @@ const initialState = { withUserLogged: false, allowedAgents: [], logtestToken: '', - restartAttempt: 0, - syncCheckAttempt: 0, - unsynchronizedNodes: [], - restartStatus: '' }; const appStateReducers = (state = initialState, action) => { @@ -143,7 +139,7 @@ const appStateReducers = (state = initialState, action) => { ...state, withUserLogged: action.withUserLogged, }; - } + } if (action.type === 'GET_ALLOWED_AGENTS') { return { @@ -151,7 +147,7 @@ const appStateReducers = (state = initialState, action) => { allowedAgents: action.allowedAgents }; } - + if (action.type === 'UPDATE_LOGTEST_TOKEN') { return { ...state, @@ -159,34 +155,6 @@ const appStateReducers = (state = initialState, action) => { }; } - if (action.type === 'UPDATE_RESTART_ATTEMPT') { - return { - ...state, - restartAttempt: action.restartAttempt - }; - } - - if (action.type === 'UPDATE_SYNC_CHECK_ATTEMPT') { - return { - ...state, - syncCheckAttempt: action.syncCheckAttempt - }; - } - - if (action.type === 'UPDATE_UNSYNCHRONIZED_NODES') { - return { - ...state, - unsynchronizedNodes: action.unsynchronizedNodes - }; - } - - if (action.type === 'UPDATE_RESTART_STATUS') { - return { - ...state, - restartStatus: action.restartStatus - }; - } - return state; }; diff --git a/public/services/check-daemon-status.js b/public/services/check-daemon-status.js index b8acb3ff8f..558df37dd0 100644 --- a/public/services/check-daemon-status.js +++ b/public/services/check-daemon-status.js @@ -2,9 +2,6 @@ import store from '../redux/store'; import { updateWazuhNotReadyYet } from '../redux/actions/appStateActions'; import { WzRequest } from '../react-services/wz-request'; -/** - * @deprecated - */ export class CheckDaemonsStatus { constructor($rootScope, $timeout) { this.$rootScope = $rootScope; diff --git a/public/services/config-handler.js b/public/services/config-handler.js new file mode 100644 index 0000000000..e9547e35fe --- /dev/null +++ b/public/services/config-handler.js @@ -0,0 +1,142 @@ +/* + * Wazuh app - Group handler 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 { WzRequest } from '../react-services/wz-request'; +import { ErrorHandler } from '../react-services/error-handler'; + +export class ConfigHandler { + constructor($rootScope, errorHandler) { + this.$rootScope = $rootScope; + this.errorHandler = errorHandler; + } + + /** + * Send ossec.conf content for manager (single-node API call) + * @param {*} content XML raw content for ossec.conf file + */ + async saveManagerConfiguration(content) { + try { + const result = await WzRequest.apiReq( + 'PUT', + `/manager/configuration`, + { content, origin: 'xmleditor' } + ); + return result; + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Send ossec.conf content for a cluster node + * @param {*} node Node name + * @param {*} content XML raw content for ossec.conf file + */ + async saveNodeConfiguration(node, content) { + try { + const result = await WzRequest.apiReq( + 'PUT', + `/cluster/${node}/configuration`, + { content, origin: 'xmleditor' } + ); + return result; + } catch (error) { + return Promise.reject(error); + } + } + + async performClusterRestart() { + try { + await WzRequest.apiReq('PUT', `/cluster/restart`, { delay: 15000 }); + this.$rootScope.$broadcast('removeRestarting', {}); + } catch (error) { + this.$rootScope.$broadcast('removeRestarting', {}); + throw new Error('Error restarting cluster'); + } + } + + /** + * Restart manager (single-node API call) + */ + async restartManager() { + try { + const validationError = await WzRequest.apiReq( + 'GET', + `/manager/configuration/validation`, + {} + ); + + const data = ((validationError || {}).data || {}).data || {}; + const isOk = data.status === 'OK'; + if (!isOk && Array.isArray(data.details)) { + const str = data.details.join(); + throw new Error(str); + } + + const result = await WzRequest.apiReq('PUT', `/manager/restart`, {}); + return result; + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Restart cluster + */ + async restartCluster() { + try { + const validationError = await WzRequest.apiReq( + 'GET', + `/cluster/configuration/validation`, + {} + ); + + const data = ((validationError || {}).data || {}).data || {}; + const isOk = data.status === 'OK'; + if (!isOk && Array.isArray(data.details)) { + const str = data.details.join(); + throw new Error(str); + } + this.performClusterRestart(); + return { data: { data: 'Restarting cluster' } }; + } catch (error) { + return Promise.reject(error); + } + } + + /** + * Restart a cluster node + */ + async restartNode(node) { + try { + const validationError = await WzRequest.apiReq( + 'GET', + `/cluster/${node}/configuration/validation`, + {} + ); + + const data = ((validationError || {}).data || {}).data || {}; + const isOk = data.status === 'OK'; + if (!isOk && Array.isArray(data.details)) { + const str = data.details.join(); + throw new Error(str); + } + const result = await WzRequest.apiReq( + 'PUT', + `/cluster/${node}/restart`, + {} + ); + return result; + } catch (error) { + return Promise.reject(error); + } + } +} diff --git a/public/services/index.js b/public/services/index.js index f74440a36d..a82f4124c9 100644 --- a/public/services/index.js +++ b/public/services/index.js @@ -18,6 +18,7 @@ import { ReportingService } from './reporting'; import { VisFactoryService } from './vis-factory-handler'; import './region-maps'; import './order-object-by'; +import { ConfigHandler } from './config-handler'; import { CheckDaemonsStatus } from './check-daemon-status'; import { getAngularModule } from '../kibana-services'; @@ -29,4 +30,5 @@ app .service('commonData', CommonData) .service('reportingService', ReportingService) .service('visFactoryService', VisFactoryService) + .service('configHandler', ConfigHandler) .service('checkDaemonsStatus', CheckDaemonsStatus); diff --git a/public/styles/common.scss b/public/styles/common.scss index f7effee792..12c46a1b1e 100644 --- a/public/styles/common.scss +++ b/public/styles/common.scss @@ -1147,41 +1147,6 @@ wz-xml-file-editor { margin: 0px; } -.wz-modal-restart { - border: 1px solid #D3DAE6; - box-shadow: 0 40px 64px 0 rgba(65, 78, 101, 0.1), 0 24px 32px 0 rgba(65, 78, 101, 0.1), 0 16px 16px 0 rgba(65, 78, 101, 0.1), 0 8px 8px 0 rgba(65, 78, 101, 0.1), 0 4px 4px 0 rgba(65, 78, 101, 0.1), 0 2px 2px 0 rgba(65, 78, 101, 0.1); - border-color: #c6cad1; - border-top-color: #e3e4e8; - border-bottom-color: #aaafba; - background-color: #FFF; - border-radius: 4px; - min-width: 500px; -} - -.wz-modal-restart-error { - border: 1px solid #D3DAE6; - box-shadow: 0 40px 64px 0 rgba(65, 78, 101, 0.1), 0 24px 32px 0 rgba(65, 78, 101, 0.1), 0 16px 16px 0 rgba(65, 78, 101, 0.1), 0 8px 8px 0 rgba(65, 78, 101, 0.1), 0 4px 4px 0 rgba(65, 78, 101, 0.1), 0 2px 2px 0 rgba(65, 78, 101, 0.1); - border-color: #c6cad1; - border-top-color: #e3e4e8; - border-bottom-color: #aaafba; - background-color: rgb(248, 233, 233); - border-radius: 4px; - min-width: 400px; -} - -.wz-modal-restart-title { - margin-top: 20px; - margin-bottom: 15px; - font-size: 1.6rem; - line-height: 2.2rem; - font-weight: 700; - color: #1a1c21; -} - -.wz-modal-restart-logo { - width: 50px; -} - .header-global-wrapper + .app-wrapper:not(.hidden-chrome) { top: 48px!important; left: 48px!important;