From 6648078c05f21e1cd284de9d8352db4e6571e765 Mon Sep 17 00:00:00 2001 From: Desvelao Date: Fri, 23 Apr 2021 18:12:44 +0200 Subject: [PATCH 01/13] refactor(healthcheck): Adapt the refactorized health check component to last changes - Added monitoring and statistics index patterns checks - Added logic to retry the checks with a refresh button - Apply the retry to API connection check - Export react services in index file - Create appConfig in the Redux store --- public/app.js | 7 +- public/components/common/hooks/index.ts | 2 + .../components/common/hooks/use-app-config.ts | 19 ++ public/components/eui-loader.js | 2 +- .../__snapshots__/check-result.test.tsx.snap | 53 ++++++ .../components/check-result.test.tsx | 81 ++++++++ .../health-check/components/check-result.tsx | 132 +++++++++++++ .../health-check.container.test.tsx.snap | 104 +++++++++++ .../container/health-check.container.test.tsx | 85 +++++++++ .../container/health-check.container.tsx | 174 ++++++++++++++++++ public/components/health-check/index.ts | 1 + .../services/check-api.service.ts | 4 +- .../services/check-fields.service.ts | 22 +++ .../services/check-kibana-settings.ts | 50 +++++ .../services/check-pattern-support.service.ts | 43 +++++ .../services/check-pattern.service.ts | 37 ++++ .../services/check-setup.service.ts | 44 +++++ .../services/check-template.service.ts | 30 +++ .../services/check-time-filter.ts | 62 +++++++ .../components/health-check/services/index.ts | 8 + public/react-services/index.ts | 28 ++- .../react-services/load-app-config.service.ts | 41 +++++ public/redux/actions/app-config.actions.ts | 34 ++++ public/redux/reducers/app-config.reducer.ts | 56 ++++++ public/redux/reducers/rootReducers.js | 2 + public/redux/types.ts | 17 ++ 26 files changed, 1128 insertions(+), 10 deletions(-) create mode 100644 public/components/common/hooks/use-app-config.ts create mode 100644 public/components/health-check/components/__snapshots__/check-result.test.tsx.snap create mode 100644 public/components/health-check/components/check-result.test.tsx create mode 100644 public/components/health-check/components/check-result.tsx create mode 100644 public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap create mode 100644 public/components/health-check/container/health-check.container.test.tsx create mode 100644 public/components/health-check/container/health-check.container.tsx create mode 100644 public/components/health-check/index.ts create mode 100644 public/components/health-check/services/check-fields.service.ts create mode 100644 public/components/health-check/services/check-kibana-settings.ts create mode 100644 public/components/health-check/services/check-pattern-support.service.ts create mode 100644 public/components/health-check/services/check-pattern.service.ts create mode 100644 public/components/health-check/services/check-setup.service.ts create mode 100644 public/components/health-check/services/check-template.service.ts create mode 100644 public/components/health-check/services/check-time-filter.ts create mode 100644 public/components/health-check/services/index.ts create mode 100644 public/react-services/load-app-config.service.ts create mode 100644 public/redux/actions/app-config.actions.ts create mode 100644 public/redux/reducers/app-config.reducer.ts create mode 100644 public/redux/types.ts diff --git a/public/app.js b/public/app.js index ee573bafed..c7f760a0e0 100644 --- a/public/app.js +++ b/public/app.js @@ -53,7 +53,7 @@ import './factories'; import { checkCurrentSecurityPlatform } from './controllers/management/components/management/configuration/utils/wz-fetch'; import store from './redux/store'; import { updateCurrentPlatform } from './redux/actions/appStateActions'; -import { WzAuthentication } from './react-services/wz-authentication'; +import { WzAuthentication, loadAppConfig } from './react-services'; import { getAngularModule } from './kibana-services'; const app = getAngularModule(); @@ -81,10 +81,13 @@ app.run([ // Set currentSecurity platform in Redux when app starts. checkCurrentSecurityPlatform().then((item) => { store.dispatch(updateCurrentPlatform(item)) - }).catch(() => {}) + }).catch(() => {}); // Init the process of refreshing the user's token when app start. checkPluginVersion().finally(WzAuthentication.refresh); + + // Load the app state + loadAppConfig(); }, ]); diff --git a/public/components/common/hooks/index.ts b/public/components/common/hooks/index.ts index 63537e4229..4d1599874d 100644 --- a/public/components/common/hooks/index.ts +++ b/public/components/common/hooks/index.ts @@ -31,3 +31,5 @@ export { useRefreshAngularDiscover } from './useResfreshAngularDiscover'; export { useAllowedAgents } from './useAllowedAgents'; export { useApiRequest } from './useApiRequest'; + +export * from './use-app-config'; diff --git a/public/components/common/hooks/use-app-config.ts b/public/components/common/hooks/use-app-config.ts new file mode 100644 index 0000000000..9a323a7050 --- /dev/null +++ b/public/components/common/hooks/use-app-config.ts @@ -0,0 +1,19 @@ +/* + * Wazuh app - React hook for app configuration + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import { AppRootState } from '../../../redux/types'; +import { useSelector } from 'react-redux'; + +export const useAppConfig = () => { + const appConfig = useSelector((state: AppRootState) => state.appConfig); + return appConfig; +} diff --git a/public/components/eui-loader.js b/public/components/eui-loader.js index ec39ea8ca2..76bbebdf7e 100644 --- a/public/components/eui-loader.js +++ b/public/components/eui-loader.js @@ -30,7 +30,7 @@ import { import { Tabs } from './common/tabs/tabs'; import { MultipleAgentSelector } from './management/groups/multiple-agent-selector'; import { NodeList } from './management/cluster/node-list'; -import { HealthCheck } from './health-check/health-check'; +import { HealthCheck } from './health-check'; import { WzEmptyPromptNoPermissions } from './common/permissions/prompt'; import { getAngularModule } from '../kibana-services'; import { Logtest } from '../directives/wz-logtest/components/logtest'; diff --git a/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap b/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap new file mode 100644 index 0000000000..f8066681aa --- /dev/null +++ b/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap @@ -0,0 +1,53 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Check result component should render a Check result screen 1`] = ` + + +
+ test chest +
+
+ +
+ + + + + Checking... + +
+
+
+`; diff --git a/public/components/health-check/components/check-result.test.tsx b/public/components/health-check/components/check-result.test.tsx new file mode 100644 index 0000000000..991d59b586 --- /dev/null +++ b/public/components/health-check/components/check-result.test.tsx @@ -0,0 +1,81 @@ +/* + * Wazuh app - Check Result Component - Test + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ +import React from 'react'; +import { mount } from 'enzyme'; +import { CheckResult } from './check-result'; + +describe('Check result component', () => { + const validationService = jest.fn(); + const handleErrors = jest.fn(); + const handleCheckReady = jest.fn(); + + test('should render a Check result screen', () => { + validationService.mockImplementation(() => ({ errors: [] })); + const component = mount( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('should print ready', () => { + validationService.mockImplementation(() => ({ errors: [] })); + const component = mount( + + ); + setImmediate(() => { + expect(component.find('EuiDescriptionListDescription').find('span').at(0).text().trim()).toBe('Ready'); + }); + }); + + test('should print error', () => { + validationService.mockImplementation(() => ({ errors: ['error'] })); + const component = mount( + + ); + setImmediate(() => { + expect(component.find('EuiDescriptionListDescription').find('span').at(0).text().trim()).toBe('Error'); + }); + }); +}); diff --git a/public/components/health-check/components/check-result.tsx b/public/components/health-check/components/check-result.tsx new file mode 100644 index 0000000000..3edddd6a75 --- /dev/null +++ b/public/components/health-check/components/check-result.tsx @@ -0,0 +1,132 @@ +/* + * Wazuh app - Check Result Component + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ +import React, { useEffect, useState } from 'react'; +import { + EuiButtonIcon, + EuiDescriptionListDescription, + EuiDescriptionListTitle, + EuiIcon, + EuiLoadingSpinner, + EuiToolTip +} from '@elastic/eui'; + +type Result = 'loading' | 'ready' | 'error' | 'error_retry' | 'disabled' | 'depends_on'; + +export function CheckResult(props) { + const [result, setResult] = useState('loading'); + const [isCheckStarted, setIsCheckStarted] = useState(false); + + useEffect(() => { + if (props.check && !props.isLoading && awaitForIsReady()){ + initCheck(); + } else if (props.check === false) { + setResult('disabled'); + setAsReady(); + } + }, [props.check, props.isLoading, props.checksReady]); + + const setAsReady = () => { + props.handleCheckReady(props.name, true); + }; + + const handleErrors = (errors, parsed?) => { + props.handleErrors(props.name, errors, parsed); + }; + + /** + * validate if the current check is not started and if the dependentes checks are ready + */ + const awaitForIsReady = () => { + return !isCheckStarted && (props.awaitFor.length === 0 || props.awaitFor.every((check) => { + return props.checksReady[check]; + })) + } + + const initCheck = async () => { + setIsCheckStarted(true); + setResult('loading'); + props.cleanErrors(props.name); + try { + const { errors } = await props.validationService(); + if (errors.length) { + handleErrors(errors); + setResult('error'); + props.canRetry ? setResult('error_retry') : setResult('error'); + } else { + setResult('ready'); + setAsReady(); + } + } catch (error) { + handleErrors([error], true); + props.canRetry ? setResult('error_retry') : setResult('error'); + } + }; + + const renderResult = () => { + switch (result) { + case 'loading': + return ( + + Checking... + + ); + case 'disabled': + return Disabled; + case 'ready': + return ( + + Ready + + ); + case 'error': + return ( + + Error + + ); + case 'error_retry': + return ( + + Error + + + + + ); + case 'depends_on': + return ( + + Depends on... + + ); + } + }; + + return ( + <> + {props.title} + {renderResult()} + + ); +} diff --git a/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap b/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap new file mode 100644 index 0000000000..bbeccf5416 --- /dev/null +++ b/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap @@ -0,0 +1,104 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Health Check container should render a Health check screen 1`] = ` +
+ + +
+ + + + + + + +
+ + +
+`; diff --git a/public/components/health-check/container/health-check.container.test.tsx b/public/components/health-check/container/health-check.container.test.tsx new file mode 100644 index 0000000000..9ef11b5ba9 --- /dev/null +++ b/public/components/health-check/container/health-check.container.test.tsx @@ -0,0 +1,85 @@ +/* + * Wazuh app - Health Check Component - Test + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import { HealthCheck } from './health-check.container'; + +jest.mock('../../../react-services/error-handler', () => ({ + handle: (error) => error, +})); + +jest.mock('../../../components/common/hooks/use-app-config', () => ({ + useAppConfig: () => ({ + isReady: true, + isLoading: false, + data: { + 'ip.selector': 1, + 'checks.metaFields': true, + 'checks.timerFilter': true, + 'checks.api': true, + 'checks.setup': true, + 'checks.pattern': true, + 'checks.template': true, + 'checks.fields': true, + }, + }), +})); + +jest.mock('../services', () => ({ + checkPatternService: () => ({ errors: [] }), + checkTemplateService: () => ({ errors: [] }), + checkApiService: () => ({ errors: [] }), + checkSetupService: () => ({ errors: [] }), + checkFieldsService: () => ({ errors: [] }), + checkKibanaSettings: () => ({ errors: [] }), + checkKibanaSettingsTimeFilter: () => ({ errors: [] }), +})); + +jest.mock('../components/check-result', () => ({ + CheckResult: () => () => <>, +})); + +jest.mock('../../../react-services/app-state', () => ({ + setPatternSelector: () => {}, +})); + +jest.mock('../../../components/common/hooks/use-app-deps', () => ({ + useAppDeps: () => ({ + core: { + http: { + basePath: { + prepend: (url) => url, + }, + }, + }, + }), +})); + +describe('Health Check container', () => { + test('should render a Health check screen', () => { + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + test('should render a Health check screen with error', () => { + const component = mount(); + + component.find('CheckResult').at(1).invoke('handleErrors')(['test']); // invoke is wrapped with act to await for setState + + const callOutError = component.find('EuiCallOut'); + expect(callOutError.text()).toBe('test'); + }); +}); diff --git a/public/components/health-check/container/health-check.container.tsx b/public/components/health-check/container/health-check.container.tsx new file mode 100644 index 0000000000..5004140db8 --- /dev/null +++ b/public/components/health-check/container/health-check.container.tsx @@ -0,0 +1,174 @@ +/* + * Wazuh app - Health Check Component + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + +import { + EuiButton, + EuiCallOut, + EuiDescriptionList, + EuiSpacer, +} from '@elastic/eui'; +import React, { Fragment, useState, useEffect } from 'react'; +import { AppState, ErrorHandler } from '../../../react-services'; +import { useAppConfig } from '../../../components/common/hooks'; +import { + checkPatternService, + checkTemplateService, + checkApiService, + checkSetupService, + checkFieldsService, + checkKibanaSettings, + checkKibanaSettingsTimeFilter, + checkPatternSupportService +} from '../services'; +import { CheckResult } from '../components/check-result'; +import { withReduxProvider } from '../../common/hocs'; +import { getHttp } from '../../../kibana-services'; +import { WAZUH_INDEX_TYPE_MONITORING, WAZUH_INDEX_TYPE_STATISTICS } from '../../../../common/constants'; + +const checks = { + api: { + title: 'Check Wazuh API connection', + validator: checkApiService, + awaitFor: [], + canRetry: true + }, + setup: { + title: 'Check for Wazuh API version', + validator: checkSetupService, + awaitFor: ["api"], + }, + pattern: { + title: 'Check Elasticsearch index pattern', + validator: checkPatternService, + awaitFor: [] + }, + template: { + title: 'Check Elasticsearch template', + validator: checkTemplateService, + awaitFor: ["pattern"], + }, + fields: { + title: 'Check index pattern fields', + validator: checkFieldsService, + awaitFor: ["pattern"], + }, + patternMonitoring: { + title: 'Check Monitoring index pattern', + validator: (appConfig) => checkPatternSupportService(appConfig.data['wazuh.monitoring.pattern'], WAZUH_INDEX_TYPE_MONITORING), + awaitFor: [], + shouldCheck: true + }, + patternStatistics: { + title: 'Check Statistics index pattern', + validator: (appConfig) => checkPatternSupportService(`${appConfig.data['cron.prefix']}-${appConfig.data['cron.statistics.index.name']}-*`, WAZUH_INDEX_TYPE_STATISTICS), + awaitFor: [], + shouldCheck: true + } +}; + +export const HealthCheck = withReduxProvider(function HealthCheck() { + const [checkErrors, setCheckErrors] = useState<{[key:string]: []}>({}); + const [checksReady, setChecksReady] = useState<{[key: string]: boolean}>({}); + const appConfig = useAppConfig(); + + useEffect(() => { + if (appConfig.isReady) { + checkKibanaSettings(appConfig.data['checks.metaFields']); + checkKibanaSettingsTimeFilter(appConfig.data['checks.timeFilter']); + AppState.setPatternSelector(appConfig.data['ip.selector']); + } + }, [appConfig]); + + useEffect(() => { + // Redirect to app + Object.keys(checks) + .every(check => checksReady[check]) && (window.location.href = getHttp().basePath.prepend('/app/wazuh#/overview')); + }, [checksReady, appConfig]) + + const handleErrors = (checkID, errors, parsed) => { + const newErrors = parsed + ? errors.map((error) => + ErrorHandler.handle(error, 'Health Check', { warning: false, silent: true }) + ) + : errors; + setCheckErrors((prev) => ({...prev, [checkID]: newErrors})); + }; + + const cleanErrors = (checkID: string) => { + delete checkErrors[checkID]; + setCheckErrors({...checkErrors}); + } + + const handleCheckReady = (checkID, isReady) => { + setChecksReady(prev => ({...prev, [checkID]: isReady})); + } + + const renderChecks = () => { + return Object.keys(checks).map((check, index) => { + return ( + checks[check].validator(appConfig)} + handleErrors={handleErrors} + cleanErrors={cleanErrors} + isLoading={appConfig.isLoading} + handleCheckReady= {handleCheckReady} + checksReady={checksReady} + canRetry={checks[check].canRetry} + /> + ); + }); + }; + + const renderErrors = () => { + return Object.keys(checkErrors).map((checkID) => + checkErrors[checkID].map((error, index) => ( + + + + + )) + ) + }; + + const logo_url = getHttp().basePath.prepend('/plugins/wazuh/assets/icon_blue.svg'); + return ( +
+ +
+ + {renderChecks()} + +
+ + {renderErrors()} + + {Object.keys(checkErrors).length > 0 ? ( + + Go to App + + ): null} +
+ ); +}) diff --git a/public/components/health-check/index.ts b/public/components/health-check/index.ts new file mode 100644 index 0000000000..4391723e0a --- /dev/null +++ b/public/components/health-check/index.ts @@ -0,0 +1 @@ +export { HealthCheck } from './container/health-check.container'; diff --git a/public/components/health-check/services/check-api.service.ts b/public/components/health-check/services/check-api.service.ts index 4a0373d563..a5ff226dbe 100644 --- a/public/components/health-check/services/check-api.service.ts +++ b/public/components/health-check/services/check-api.service.ts @@ -13,9 +13,7 @@ */ import { getToasts } from '../../../kibana-services'; -import AppState from '../../../react-services/app-state'; -import GenericRequest from '../../../react-services/generic-request'; -import ApiCheck from '../../../react-services/wz-api-check'; +import { ApiCheck, AppState, GenericRequest } from '../../../react-services'; const trySetDefault = async () => { try { diff --git a/public/components/health-check/services/check-fields.service.ts b/public/components/health-check/services/check-fields.service.ts new file mode 100644 index 0000000000..c144e66986 --- /dev/null +++ b/public/components/health-check/services/check-fields.service.ts @@ -0,0 +1,22 @@ +/* + * Wazuh app - Check Fields Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + +import { PatternHandler } from '../../../react-services'; + +export const checkFieldsService = async (): Promise<{ errors: string[] }> => { + let errors: string[] = []; + await PatternHandler.refreshIndexPattern(); + + return { errors }; +}; diff --git a/public/components/health-check/services/check-kibana-settings.ts b/public/components/health-check/services/check-kibana-settings.ts new file mode 100644 index 0000000000..00fd2c3455 --- /dev/null +++ b/public/components/health-check/services/check-kibana-settings.ts @@ -0,0 +1,50 @@ +/* + * Wazuh app - Check Kibana settings Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + +import { AxiosResponse } from 'axios'; +import { GenericRequest } from '../../../react-services'; + +type userValue = { userValue: T }; + +type kbnSettings = { + buildNum: userValue; + metaFields?: userValue; +}; + +type responseKbnSettings = { settings: kbnSettings }; + +export function checkKibanaSettings(removeMetaFields: boolean) { + removeMetaFields && + getKibanaSettings() + .then(checkMetafieldSetting) + .then(updateMetaFieldsSetting) + .catch((error) => error !== 'Unable to update config' && console.log(error)); +} + +async function getKibanaSettings(): Promise { + const kibanaSettings: AxiosResponse = await GenericRequest.request('GET', '/api/kibana/settings'); + return kibanaSettings.data; +} + +async function checkMetafieldSetting({ settings }: responseKbnSettings) { + const { metaFields } = settings; + return !!metaFields && !!metaFields.userValue.length; +} + +async function updateMetaFieldsSetting(isModified: boolean) { + return ( + !isModified && + (await GenericRequest.request('POST', '/api/kibana/settings', { changes: { metaFields: [] } })) + ); +} diff --git a/public/components/health-check/services/check-pattern-support.service.ts b/public/components/health-check/services/check-pattern-support.service.ts new file mode 100644 index 0000000000..80bc0d4f7c --- /dev/null +++ b/public/components/health-check/services/check-pattern-support.service.ts @@ -0,0 +1,43 @@ +/* + * Wazuh app - Check Pattern Support Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ +import { SavedObject } from '../../../react-services'; +import { getToasts } from '../../../kibana-services'; + +export const checkPatternSupportService = async (pattern: string, indexType : string): Promise<{ errors: string[] }> => { + let errors: string[] = []; + const result = await SavedObject.existsIndexPattern(pattern); + if (!result.data) { + const toast = getToasts().addWarning(`${pattern} index pattern was not found and it will be created`); + const fields = await SavedObject.getIndicesFields(pattern, indexType); + try { + await SavedObject.createSavedObject( + 'index-pattern', + pattern, + { + attributes: { + title: pattern, + timeFieldName: 'timestamp' + } + }, + fields + ); + getToasts().remove(toast.id); + getToasts().addSuccess(`${pattern} index pattern created successfully`); + } catch (error) { + getToasts().remove(toast.id); + errors.push(`Error trying to create ${pattern} index pattern: ${error.message}`); + } + }; + return { errors }; +} diff --git a/public/components/health-check/services/check-pattern.service.ts b/public/components/health-check/services/check-pattern.service.ts new file mode 100644 index 0000000000..cda2878359 --- /dev/null +++ b/public/components/health-check/services/check-pattern.service.ts @@ -0,0 +1,37 @@ +/* + * Wazuh app - Check Pattern Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ +import { AppState, SavedObject, PatternHandler } from '../../../react-services'; +import { getDataPlugin } from '../../../kibana-services'; +import { HEALTH_CHECK } from '../../../../common/constants'; + +export const checkPatternService = async (appConfig): Promise<{ errors: string[] }> => { + const patternId = AppState.getCurrentPattern(); + let errors: string[] = []; + const existsDefaultPattern = await SavedObject.existsIndexPattern(appConfig.data['pattern']); + existsDefaultPattern.status && getDataPlugin().indexPatterns.setDefault(appConfig.data['pattern'], true); + let patternData = patternId ? await SavedObject.existsIndexPattern(patternId) : false; + if (!patternData) patternData = {}; + + if (!patternData.status) { + const patternList = await PatternHandler.getPatternList(HEALTH_CHECK); + if (patternList.length) { + const indexPatternDefault = patternList.find((indexPattern) => indexPattern.title === appConfig.data['pattern']); + indexPatternDefault && AppState.setCurrentPattern(indexPatternDefault.id); + return await checkPatternService(appConfig); + } else { + errors.push('The selected index-pattern is not present.'); + } + } + return { errors }; +}; diff --git a/public/components/health-check/services/check-setup.service.ts b/public/components/health-check/services/check-setup.service.ts new file mode 100644 index 0000000000..e8d8ad8a3c --- /dev/null +++ b/public/components/health-check/services/check-setup.service.ts @@ -0,0 +1,44 @@ +/* + * Wazuh app - Check Setup Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + +import { AppState, GenericRequest, WzRequest } from '../../../react-services'; + +export const checkSetupService = async (): Promise<{ errors: string[] }> => { + let errors: string[] = []; + const currentApi = JSON.parse(AppState.getCurrentAPI() || '{}'); + if (currentApi && currentApi.id) { + const versionData = await WzRequest.apiReq('GET', '//', {}); + const apiVersion = versionData.data.data['api_version']; + const setupData = await GenericRequest.request('GET', '/api/setup'); + if (!setupData.data.data['app-version']) { + errors.push('Error fetching app version'); + } + if (!apiVersion) { + errors.push('Error fetching Wazuh API version'); + } else { + const api = /v?(?\d+)\.(?\d+)\.(?\d+)/.exec(apiVersion); + const appSplit = setupData.data.data['app-version'].split('.'); + if ( + !api || + !api.groups || + api.groups.version !== appSplit[0] || + api.groups.minor !== appSplit[1] + ) { + errors.push('API version mismatch. Expected v' + setupData.data.data['app-version']); + } + } + } + + return { errors }; +}; diff --git a/public/components/health-check/services/check-template.service.ts b/public/components/health-check/services/check-template.service.ts new file mode 100644 index 0000000000..9ffe268208 --- /dev/null +++ b/public/components/health-check/services/check-template.service.ts @@ -0,0 +1,30 @@ +/* + * Wazuh app - Check Template Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + +import { AppState, GenericRequest, SavedObject } from '../../../react-services'; + +export const checkTemplateService = async (): Promise<{ errors: string[] }> => { + let errors: string[] = []; + const patternId = AppState.getCurrentPattern(); + let patternTitle = ''; + let patternData = patternId ? await SavedObject.existsIndexPattern(patternId) : false; + if (!patternData) patternData = {}; + patternTitle = patternData.title; + + const templateData = await GenericRequest.request('GET', `/elastic/template/${patternTitle}`); + if (!templateData.data.status) { + errors.push(`No template found for the selected index-pattern [${patternTitle}]`); + } + return { errors }; +}; diff --git a/public/components/health-check/services/check-time-filter.ts b/public/components/health-check/services/check-time-filter.ts new file mode 100644 index 0000000000..55e16cae85 --- /dev/null +++ b/public/components/health-check/services/check-time-filter.ts @@ -0,0 +1,62 @@ +/* + * Wazuh app - Check Kibana time filter setting Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + +import { AxiosResponse } from 'axios'; +import { GenericRequest } from '../../../react-services'; +import { WAZUH_TIME_FILTER_DEFAULT } from '../../../../common/constants'; +import { getDataPlugin } from '../../../kibana-services'; + +type userValue = { userValue: T }; +type kbnSettings = { + buildNum: userValue; + timeFilter?: userValue; +}; + +type responseKbnSettings = { settings: kbnSettings }; + +export function checkKibanaSettingsTimeFilter(changeTimeDefaults: boolean) { + changeTimeDefaults && + getKibanaSettings() + .then(checkTimeFilter) + .then(updateTimeFilterSetting) + .catch((error) => error !== 'Unable to update config' && console.log(error)); +} + +async function getKibanaSettings(): Promise { + const kibanaSettings: AxiosResponse = await GenericRequest.request('GET', '/api/kibana/settings'); + return kibanaSettings.data; +} + +async function checkTimeFilter({ settings }: responseKbnSettings) { + if (!settings['timepicker:timeDefaults']) { + return false; + } + + const timeFilter = settings['timepicker:timeDefaults'].userValue; + const timeFilterObject = JSON.parse(timeFilter); + return ( + WAZUH_TIME_FILTER_DEFAULT.from == timeFilterObject.from && + WAZUH_TIME_FILTER_DEFAULT.to == timeFilterObject.to + ); +} + +async function updateTimeFilterSetting(isModified: boolean) { + return ( + !isModified && + (await GenericRequest.request('POST', '/api/kibana/settings', { + changes: { 'timepicker:timeDefaults': JSON.stringify(WAZUH_TIME_FILTER_DEFAULT) }, + })) && + getDataPlugin().query.timefilter.timefilter.setTime(WAZUH_TIME_FILTER_DEFAULT) + ); +} diff --git a/public/components/health-check/services/index.ts b/public/components/health-check/services/index.ts new file mode 100644 index 0000000000..620a622833 --- /dev/null +++ b/public/components/health-check/services/index.ts @@ -0,0 +1,8 @@ +export * from './check-pattern.service'; +export * from './check-template.service'; +export * from './check-setup.service'; +export * from './check-api.service'; +export * from './check-fields.service'; +export * from './check-kibana-settings'; +export * from './check-time-filter'; +export * from './check-pattern-support.service'; diff --git a/public/react-services/index.ts b/public/react-services/index.ts index a10bf3b43d..9e11c7932a 100644 --- a/public/react-services/index.ts +++ b/public/react-services/index.ts @@ -1,4 +1,24 @@ -export { GenericRequest } from './generic-request'; -export { WzRequest } from './wz-request'; -export { ErrorHandler } from './error-handler'; -export { formatUIDate } from './time-service'; \ No newline at end of file +export * from './action-agents'; +export * from './app-navigate'; +export * from './app-state'; +export * from './check-daemons-status'; +export * from './error-handler'; +export * from './filter-authorization-agents'; +export * from './generic-request'; +export * from './group-handler'; +export * from './load-app-config.service'; +export * from './pattern-handler'; +export * from './reporting'; +export * from './saved-objects'; +export * from './time-service'; +export * from './toast-notifications' +export * from './vis-factory-handler'; +export * from './wazuh-config'; +export * from './wz-agents'; +export * from './wz-api-check'; +export * from './wz-authentication'; +export * from './wz-csv'; +export * from './wz-request'; +export * from './wz-security-opendistro'; +export * from './wz-security-xpack'; +export * from './wz-user-permissions'; \ No newline at end of file diff --git a/public/react-services/load-app-config.service.ts b/public/react-services/load-app-config.service.ts new file mode 100644 index 0000000000..f2f2c7cee1 --- /dev/null +++ b/public/react-services/load-app-config.service.ts @@ -0,0 +1,41 @@ +/* + * Wazuh app - Load App config service + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import { GenericRequest } from './generic-request'; +import store from '../redux/store'; +import { + setAppConfigIsLoading, + setAppConfigHasError, + updateAppConfig, +} from '../redux/actions/app-config.actions'; + + +/** + * Retunrs the wazuh app config + */ +export const loadAppConfig = async () => { + try { + store.dispatch(setAppConfigIsLoading()); + const config = await GenericRequest.request('GET', '/utils/configuration', {}); + + if (!config || !config.data || !config.data.data) { + throw new Error('No config available'); + } + + const ymlContent = config.data.data; + store.dispatch(updateAppConfig(ymlContent)) + } catch (error) { + store.dispatch(setAppConfigHasError()); + console.error('Error parsing wazuh.yml, using default values.'); // eslint-disable-line + console.error(error.message || error); // eslint-disable-line + } +}; diff --git a/public/redux/actions/app-config.actions.ts b/public/redux/actions/app-config.actions.ts new file mode 100644 index 0000000000..bff5cd4cc1 --- /dev/null +++ b/public/redux/actions/app-config.actions.ts @@ -0,0 +1,34 @@ +/* + * Wazuh app - App Config Actions + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import { ResolverAction } from '../types'; + +export const setAppConfigIsLoading = (): ResolverAction => { + return { + type: 'UPDATE_APP_CONFIG_SET_IS_LOADING', + payload: null + }; +} + +export const setAppConfigHasError = (): ResolverAction => { + return { + type: 'UPDATE_APP_CONFIG_SET_HAS_ERROR', + payload: null + }; +} + +export const updateAppConfig = (data: object): ResolverAction => { + return { + type: 'UPDATE_APP_CONFIG_DATA', + payload: data + }; +} diff --git a/public/redux/reducers/app-config.reducer.ts b/public/redux/reducers/app-config.reducer.ts new file mode 100644 index 0000000000..2a5f408648 --- /dev/null +++ b/public/redux/reducers/app-config.reducer.ts @@ -0,0 +1,56 @@ +/* + * Wazuh app - App Config Reducer + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import { Reducer } from 'redux'; +import { WAZUH_DEFAULT_APP_CONFIG } from '../../../common/constants'; +import { AppConfigState, ResolverAction } from '../types'; + +const initialState: AppConfigState = { + isLoading: false, + isReady: false, + hasError: false, + data: WAZUH_DEFAULT_APP_CONFIG, +}; + +const appConfigReducer: Reducer = ( + state = initialState, + action +) => { + switch (action.type) { + case 'UPDATE_APP_CONFIG_SET_IS_LOADING': + return { + ...state, + isLoading: true, + isReady: false, + hasError: false + }; + case 'UPDATE_APP_CONFIG_SET_HAS_ERROR': + return { + ...state, + isLoading: false, + isReady: false, + hasError: true + }; + case 'UPDATE_APP_CONFIG_DATA': + return { + ...state, + isLoading: false, + isReady: true, + hasError: false, + data: {...state.data, ...action.payload}, + }; + default: + return state; + } +}; + +export default appConfigReducer; diff --git a/public/redux/reducers/rootReducers.js b/public/redux/reducers/rootReducers.js index 1f505d5cac..79510feead 100644 --- a/public/redux/reducers/rootReducers.js +++ b/public/redux/reducers/rootReducers.js @@ -22,6 +22,7 @@ import visualizationsReducers from './visualizationsReducers'; import globalBreadcrumbReducers from './globalBreadcrumbReducers'; import securityReducers from './securityReducers'; import toolsReducers from './toolsReducers'; +import appConfig from './app-config.reducer'; export default combineReducers({ rulesetReducers, @@ -35,4 +36,5 @@ export default combineReducers({ globalBreadcrumbReducers, securityReducers, toolsReducers, + appConfig }); diff --git a/public/redux/types.ts b/public/redux/types.ts new file mode 100644 index 0000000000..d847da1999 --- /dev/null +++ b/public/redux/types.ts @@ -0,0 +1,17 @@ +import { DefaultRootState } from 'react-redux'; + +export type ResolverAction = { + type: string; + payload: any; +}; + +export type AppConfigState = { + isLoading: boolean; + isReady: boolean; + hasError: boolean; + data: { [key: string]: any }; +}; + +export type AppRootState = DefaultRootState & { + appConfig: AppConfigState, +}; From ce831c725ab4c72917b1181e79aaf10ce6806a3b Mon Sep 17 00:00:00 2001 From: Desvelao Date: Mon, 26 Apr 2021 12:34:13 +0200 Subject: [PATCH 02/13] feat(healtcheck): Replace health check initial state to waiting --- .../components/health-check/components/check-result.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/components/health-check/components/check-result.tsx b/public/components/health-check/components/check-result.tsx index 3edddd6a75..1135f3e0c7 100644 --- a/public/components/health-check/components/check-result.tsx +++ b/public/components/health-check/components/check-result.tsx @@ -21,10 +21,10 @@ import { EuiToolTip } from '@elastic/eui'; -type Result = 'loading' | 'ready' | 'error' | 'error_retry' | 'disabled' | 'depends_on'; +type Result = 'loading' | 'ready' | 'error' | 'error_retry' | 'disabled' | 'waiting'; export function CheckResult(props) { - const [result, setResult] = useState('loading'); + const [result, setResult] = useState('waiting'); const [isCheckStarted, setIsCheckStarted] = useState(false); useEffect(() => { @@ -114,10 +114,10 @@ export function CheckResult(props) { ); - case 'depends_on': + case 'waiting': return ( - Depends on... + Waiting... ); } From 95c8078ecbaadd22f3734d652386e1f7cd9dd505 Mon Sep 17 00:00:00 2001 From: Desvelao Date: Mon, 26 Apr 2021 12:35:04 +0200 Subject: [PATCH 03/13] fet(healthcheck): Add can retry to healthcheck checks --- .../container/health-check.container.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/public/components/health-check/container/health-check.container.tsx b/public/components/health-check/container/health-check.container.tsx index 5004140db8..ebbf5622c9 100644 --- a/public/components/health-check/container/health-check.container.tsx +++ b/public/components/health-check/container/health-check.container.tsx @@ -41,7 +41,7 @@ const checks = { title: 'Check Wazuh API connection', validator: checkApiService, awaitFor: [], - canRetry: true + canRetry: true, }, setup: { title: 'Check for Wazuh API version', @@ -51,29 +51,34 @@ const checks = { pattern: { title: 'Check Elasticsearch index pattern', validator: checkPatternService, - awaitFor: [] + awaitFor: [], + canRetry: true, }, template: { title: 'Check Elasticsearch template', validator: checkTemplateService, awaitFor: ["pattern"], + canRetry: true, }, fields: { title: 'Check index pattern fields', validator: checkFieldsService, awaitFor: ["pattern"], + canRetry: true, }, patternMonitoring: { title: 'Check Monitoring index pattern', validator: (appConfig) => checkPatternSupportService(appConfig.data['wazuh.monitoring.pattern'], WAZUH_INDEX_TYPE_MONITORING), awaitFor: [], - shouldCheck: true + shouldCheck: true, + canRetry: true, }, patternStatistics: { title: 'Check Statistics index pattern', validator: (appConfig) => checkPatternSupportService(`${appConfig.data['cron.prefix']}-${appConfig.data['cron.statistics.index.name']}-*`, WAZUH_INDEX_TYPE_STATISTICS), awaitFor: [], - shouldCheck: true + shouldCheck: true, + canRetry: true, } }; From 31bc7009779b828646d046dc04a5b1de314f56ab Mon Sep 17 00:00:00 2001 From: Desvelao Date: Mon, 26 Apr 2021 12:37:18 +0200 Subject: [PATCH 04/13] fix(menu): Fix error in toast from WzMenu and revome unnecessary return in PatternHandler --- public/components/wz-menu/wz-menu.js | 4 ++-- public/react-services/pattern-handler.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/public/components/wz-menu/wz-menu.js b/public/components/wz-menu/wz-menu.js index 56465f78ba..40f8ec915c 100644 --- a/public/components/wz-menu/wz-menu.js +++ b/public/components/wz-menu/wz-menu.js @@ -211,7 +211,7 @@ class WzMenu extends Component { this.setState({ currentMenuTab: currentTab, hover: currentTab }); } const list = await PatternHandler.getPatternList('api'); - if (!list) return; + if (!list || (list && !list.length)) return; // Abort if we have disabled the pattern selector if (!AppState.getPatternSelector()) return; @@ -241,7 +241,7 @@ class WzMenu extends Component { }); } } catch (error) { - this.showToast('danger', 'Error', error, 4000); + this.showToast('danger', 'Error', error.message || error, 4000); } this.isLoading = false; } diff --git a/public/react-services/pattern-handler.js b/public/react-services/pattern-handler.js index 95d650c4ba..39c40730bc 100644 --- a/public/react-services/pattern-handler.js +++ b/public/react-services/pattern-handler.js @@ -69,7 +69,6 @@ export class PatternHandler { console.error("getPatternList", error) throw new Error('Error Pattern Handler (getPatternList)'); } - return; } /** From 3d00e8a90c257c815be4cf335b4262dea6363631 Mon Sep 17 00:00:00 2001 From: Desvelao Date: Wed, 28 Apr 2021 12:31:23 +0200 Subject: [PATCH 05/13] fix(health-check): Fix create index pattern when change the setting in Settings > Configuration and loop in health check --- .../health-check/container/health-check.container.tsx | 4 ++-- .../health-check/services/check-pattern.service.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/public/components/health-check/container/health-check.container.tsx b/public/components/health-check/container/health-check.container.tsx index ebbf5622c9..7cfd234aa9 100644 --- a/public/components/health-check/container/health-check.container.tsx +++ b/public/components/health-check/container/health-check.container.tsx @@ -96,10 +96,10 @@ export const HealthCheck = withReduxProvider(function HealthCheck() { }, [appConfig]); useEffect(() => { - // Redirect to app + // Redirect to app when all checks are ready Object.keys(checks) .every(check => checksReady[check]) && (window.location.href = getHttp().basePath.prepend('/app/wazuh#/overview')); - }, [checksReady, appConfig]) + }, [checksReady]); const handleErrors = (checkID, errors, parsed) => { const newErrors = parsed diff --git a/public/components/health-check/services/check-pattern.service.ts b/public/components/health-check/services/check-pattern.service.ts index cda2878359..1cfbff22b1 100644 --- a/public/components/health-check/services/check-pattern.service.ts +++ b/public/components/health-check/services/check-pattern.service.ts @@ -18,13 +18,16 @@ import { HEALTH_CHECK } from '../../../../common/constants'; export const checkPatternService = async (appConfig): Promise<{ errors: string[] }> => { const patternId = AppState.getCurrentPattern(); let errors: string[] = []; + const patternList = await PatternHandler.getPatternList(HEALTH_CHECK); + const existsDefaultPattern = await SavedObject.existsIndexPattern(appConfig.data['pattern']); existsDefaultPattern.status && getDataPlugin().indexPatterns.setDefault(appConfig.data['pattern'], true); + let patternData = patternId ? await SavedObject.existsIndexPattern(patternId) : false; + if (!patternData) patternData = {}; if (!patternData.status) { - const patternList = await PatternHandler.getPatternList(HEALTH_CHECK); if (patternList.length) { const indexPatternDefault = patternList.find((indexPattern) => indexPattern.title === appConfig.data['pattern']); indexPatternDefault && AppState.setCurrentPattern(indexPatternDefault.id); From 7d83daa8284286494e2cb26de2767c460fe03b6a Mon Sep 17 00:00:00 2001 From: Desvelao Date: Wed, 28 Apr 2021 13:29:03 +0200 Subject: [PATCH 06/13] fix(health-check): renamed files from appConfig Redux actions and reducer --- public/react-services/load-app-config.service.ts | 2 +- .../actions/{app-config.actions.ts => appConfigActions.ts} | 0 .../reducers/{app-config.reducer.ts => appConfigReducers.ts} | 0 public/redux/reducers/rootReducers.js | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename public/redux/actions/{app-config.actions.ts => appConfigActions.ts} (100%) rename public/redux/reducers/{app-config.reducer.ts => appConfigReducers.ts} (100%) diff --git a/public/react-services/load-app-config.service.ts b/public/react-services/load-app-config.service.ts index f2f2c7cee1..8e09d9b6e1 100644 --- a/public/react-services/load-app-config.service.ts +++ b/public/react-services/load-app-config.service.ts @@ -16,7 +16,7 @@ import { setAppConfigIsLoading, setAppConfigHasError, updateAppConfig, -} from '../redux/actions/app-config.actions'; +} from '../redux/actions/appConfigActions'; /** diff --git a/public/redux/actions/app-config.actions.ts b/public/redux/actions/appConfigActions.ts similarity index 100% rename from public/redux/actions/app-config.actions.ts rename to public/redux/actions/appConfigActions.ts diff --git a/public/redux/reducers/app-config.reducer.ts b/public/redux/reducers/appConfigReducers.ts similarity index 100% rename from public/redux/reducers/app-config.reducer.ts rename to public/redux/reducers/appConfigReducers.ts diff --git a/public/redux/reducers/rootReducers.js b/public/redux/reducers/rootReducers.js index 79510feead..9ddfeb621b 100644 --- a/public/redux/reducers/rootReducers.js +++ b/public/redux/reducers/rootReducers.js @@ -22,7 +22,7 @@ import visualizationsReducers from './visualizationsReducers'; import globalBreadcrumbReducers from './globalBreadcrumbReducers'; import securityReducers from './securityReducers'; import toolsReducers from './toolsReducers'; -import appConfig from './app-config.reducer'; +import appConfig from './appConfigReducers'; export default combineReducers({ rulesetReducers, From 86bcaa3b8c456081ef93bc45c2309be11952332a Mon Sep 17 00:00:00 2001 From: Desvelao Date: Wed, 28 Apr 2021 14:35:46 +0200 Subject: [PATCH 07/13] fix(frontend): Replace config singleton saving to Redux --- public/react-services/wazuh-config.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/react-services/wazuh-config.js b/public/react-services/wazuh-config.js index 14af752e99..02199c5788 100644 --- a/public/react-services/wazuh-config.js +++ b/public/react-services/wazuh-config.js @@ -10,6 +10,9 @@ * Find more information about this on the LICENSE file. */ +import store from "../redux/store"; +import { updateAppConfig } from "../redux/actions/appConfigActions"; + export class WazuhConfig { constructor() { if (!!WazuhConfig.instance) { @@ -17,7 +20,6 @@ export class WazuhConfig { } WazuhConfig.instance = this; - this.config = {}; return this; } @@ -27,20 +29,20 @@ export class WazuhConfig { * @param {Object} cfg */ setConfig(cfg) { - this.config = { ...cfg }; + store.dispatch(updateAppConfig({...cfg})); } /** * Get configuration */ getConfig() { - return this.config; + return store.getState().appConfig.data; } /** * Returns true if debug level is enabled, otherwise it returns false. */ isDebug() { - return ((this.config || {})['logs.level'] || false) === 'debug'; + return ((this.getConfig() || {})['logs.level'] || false) === 'debug'; } } From 414d0f30f8946abbd0edb61ec6837eefcbfbb483 Mon Sep 17 00:00:00 2001 From: Desvelao Date: Wed, 28 Apr 2021 16:51:32 +0200 Subject: [PATCH 08/13] fix(health-check): Fix infinite loop rendering component when a check is disabled in the configuration --- public/components/health-check/components/check-result.tsx | 2 +- .../health-check/container/health-check.container.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public/components/health-check/components/check-result.tsx b/public/components/health-check/components/check-result.tsx index 1135f3e0c7..7c710a64a0 100644 --- a/public/components/health-check/components/check-result.tsx +++ b/public/components/health-check/components/check-result.tsx @@ -30,7 +30,7 @@ export function CheckResult(props) { useEffect(() => { if (props.check && !props.isLoading && awaitForIsReady()){ initCheck(); - } else if (props.check === false) { + } else if (props.check === false && !props.checksReady[props.name]) { setResult('disabled'); setAsReady(); } diff --git a/public/components/health-check/container/health-check.container.tsx b/public/components/health-check/container/health-check.container.tsx index 7cfd234aa9..8320f4ad36 100644 --- a/public/components/health-check/container/health-check.container.tsx +++ b/public/components/health-check/container/health-check.container.tsx @@ -18,7 +18,7 @@ import { EuiDescriptionList, EuiSpacer, } from '@elastic/eui'; -import React, { Fragment, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, useRef } from 'react'; import { AppState, ErrorHandler } from '../../../react-services'; import { useAppConfig } from '../../../components/common/hooks'; import { @@ -86,9 +86,11 @@ export const HealthCheck = withReduxProvider(function HealthCheck() { const [checkErrors, setCheckErrors] = useState<{[key:string]: []}>({}); const [checksReady, setChecksReady] = useState<{[key: string]: boolean}>({}); const appConfig = useAppConfig(); + const checksInitiated = useRef(false); useEffect(() => { - if (appConfig.isReady) { + if (appConfig.isReady && !checksInitiated.current) { + checksInitiated.current = true; checkKibanaSettings(appConfig.data['checks.metaFields']); checkKibanaSettingsTimeFilter(appConfig.data['checks.timeFilter']); AppState.setPatternSelector(appConfig.data['ip.selector']); From 8f2127e8023417763bd8bf6083f88df29de2537d Mon Sep 17 00:00:00 2001 From: Desvelao Date: Wed, 28 Apr 2021 17:12:08 +0200 Subject: [PATCH 09/13] fix(health-check): Rename health checks titles --- .../container/health-check.container.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/components/health-check/container/health-check.container.tsx b/public/components/health-check/container/health-check.container.tsx index 8320f4ad36..edcb1dd6c2 100644 --- a/public/components/health-check/container/health-check.container.tsx +++ b/public/components/health-check/container/health-check.container.tsx @@ -44,37 +44,37 @@ const checks = { canRetry: true, }, setup: { - title: 'Check for Wazuh API version', + title: 'Check Wazuh API version', validator: checkSetupService, awaitFor: ["api"], }, pattern: { - title: 'Check Elasticsearch index pattern', + title: 'Check alerts index pattern', validator: checkPatternService, awaitFor: [], canRetry: true, }, template: { - title: 'Check Elasticsearch template', + title: 'Check alerts indices template', validator: checkTemplateService, awaitFor: ["pattern"], canRetry: true, }, fields: { - title: 'Check index pattern fields', + title: 'Check alerts index pattern fields', validator: checkFieldsService, awaitFor: ["pattern"], canRetry: true, }, patternMonitoring: { - title: 'Check Monitoring index pattern', + title: 'Check monitoring index pattern', validator: (appConfig) => checkPatternSupportService(appConfig.data['wazuh.monitoring.pattern'], WAZUH_INDEX_TYPE_MONITORING), awaitFor: [], shouldCheck: true, canRetry: true, }, patternStatistics: { - title: 'Check Statistics index pattern', + title: 'Check statistics index pattern', validator: (appConfig) => checkPatternSupportService(`${appConfig.data['cron.prefix']}-${appConfig.data['cron.statistics.index.name']}-*`, WAZUH_INDEX_TYPE_STATISTICS), awaitFor: [], shouldCheck: true, From 965771d367bd72f3628b2752255d99e3da6d6542 Mon Sep 17 00:00:00 2001 From: Desvelao Date: Thu, 29 Apr 2021 20:50:54 +0200 Subject: [PATCH 10/13] fix(health-check): Fix the tests for Health check --- .../__snapshots__/check-result.test.tsx.snap | 16 +++++ .../components/check-result.test.tsx | 13 ++++- .../health-check.container.test.tsx.snap | 58 +++++++++++++++---- .../container/health-check.container.test.tsx | 37 ++++++------ .../container/health-check.container.tsx | 12 +++- 5 files changed, 99 insertions(+), 37 deletions(-) diff --git a/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap b/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap index f8066681aa..4e90fc53d4 100644 --- a/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap +++ b/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap @@ -3,8 +3,24 @@ exports[`Check result component should render a Check result screen 1`] = ` { const validationService = jest.fn(); const handleErrors = jest.fn(); const handleCheckReady = jest.fn(); + const cleanErrors = jest.fn(); test('should render a Check result screen', () => { validationService.mockImplementation(() => ({ errors: [] })); const component = mount( { isLoading={false} handleCheckReady={handleCheckReady} checksReady={{}} + cleanErrors={cleanErrors} + canRetry={true} /> ); @@ -44,7 +47,7 @@ describe('Check result component', () => { const component = mount( { isLoading={false} handleCheckReady={handleCheckReady} checksReady={{}} + cleanErrors={cleanErrors} + canRetry={true} /> ); setImmediate(() => { @@ -64,7 +69,7 @@ describe('Check result component', () => { const component = mount( { isLoading={false} handleCheckReady={handleCheckReady} checksReady={{}} + cleanErrors={cleanErrors} + canRetry={true} /> ); setImmediate(() => { diff --git a/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap b/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap index bbeccf5416..85608a2970 100644 --- a/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap +++ b/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap @@ -4,9 +4,6 @@ exports[`Health Check container should render a Health check screen 1`] = `
- + + diff --git a/public/components/health-check/container/health-check.container.test.tsx b/public/components/health-check/container/health-check.container.test.tsx index 9ef11b5ba9..34849ab08f 100644 --- a/public/components/health-check/container/health-check.container.test.tsx +++ b/public/components/health-check/container/health-check.container.test.tsx @@ -14,11 +14,8 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { HealthCheck } from './health-check.container'; +import { HealthCheckTest } from './health-check.container'; -jest.mock('../../../react-services/error-handler', () => ({ - handle: (error) => error, -})); jest.mock('../../../components/common/hooks/use-app-config', () => ({ useAppConfig: () => ({ @@ -45,41 +42,43 @@ jest.mock('../services', () => ({ checkFieldsService: () => ({ errors: [] }), checkKibanaSettings: () => ({ errors: [] }), checkKibanaSettingsTimeFilter: () => ({ errors: [] }), + checkPatternSupportService: () => ({ errors: [] }) })); jest.mock('../components/check-result', () => ({ CheckResult: () => () => <>, })); -jest.mock('../../../react-services/app-state', () => ({ - setPatternSelector: () => {}, +jest.mock('../../../react-services', () => ({ + AppState: { + setPatternSelector: () => {}, + }, + ErrorHandler: { + handle: (error) => error + } })); -jest.mock('../../../components/common/hooks/use-app-deps', () => ({ - useAppDeps: () => ({ - core: { - http: { - basePath: { - prepend: (url) => url, - }, - }, - }, +jest.mock('../../../kibana-services', () => ({ + getHttp: () => ({ + basePath: { + prepend: (str) => str + } }), })); describe('Health Check container', () => { test('should render a Health check screen', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); test('should render a Health check screen with error', () => { - const component = mount(); + const component = mount(); - component.find('CheckResult').at(1).invoke('handleErrors')(['test']); // invoke is wrapped with act to await for setState + component.find('CheckResult').at(1).invoke('handleErrors')('setup',['Test error']); // invoke is wrapped with act to await for setState const callOutError = component.find('EuiCallOut'); - expect(callOutError.text()).toBe('test'); + expect(callOutError.text()).toBe('Test error'); }); }); diff --git a/public/components/health-check/container/health-check.container.tsx b/public/components/health-check/container/health-check.container.tsx index edcb1dd6c2..e30c0fb634 100644 --- a/public/components/health-check/container/health-check.container.tsx +++ b/public/components/health-check/container/health-check.container.tsx @@ -82,7 +82,7 @@ const checks = { } }; -export const HealthCheck = withReduxProvider(function HealthCheck() { +function HealthCheckComponent() { const [checkErrors, setCheckErrors] = useState<{[key:string]: []}>({}); const [checksReady, setChecksReady] = useState<{[key: string]: boolean}>({}); const appConfig = useAppConfig(); @@ -125,7 +125,7 @@ export const HealthCheck = withReduxProvider(function HealthCheck() { return Object.keys(checks).map((check, index) => { return ( ); -}) +} + +export const HealthCheck = withReduxProvider(HealthCheckComponent); + +export const HealthCheckTest = HealthCheckComponent; + + From 6514bcbdc418f855ce9957a5044759afe8304440 Mon Sep 17 00:00:00 2001 From: Desvelao Date: Thu, 29 Apr 2021 21:04:19 +0200 Subject: [PATCH 11/13] changelog: Add PR to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf8acc7d3..5766959859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to the Wazuh app project will be documented in this file. +## Wazuh v4.2.0 - Kibana 7.10.2 , 7.11.2 - Revision 4202 + +### Changed +- Refactored the Health check component [#2682](https://github.com/wazuh/wazuh-kibana-app/issues/2682) + ## Wazuh v4.2.0 - Kibana 7.10.2 , 7.11.2 - Revision 4201 ### Added From b919cd8860d8a6e04ac6422b85ebf83f909deefb Mon Sep 17 00:00:00 2001 From: Desvelao Date: Mon, 3 May 2021 12:09:37 +0200 Subject: [PATCH 12/13] refactor(health-check): Request changes, add max buckets check and some improvements - Request changes - Added the max buckets check when the component is mounted - Created the `useRootScope` hook - Improved the export in the HOCs and hooks index files - Removed the `lib` folder - Removed the `health-check` old component --- public/components/common/hocs/index.ts | 32 +- public/components/common/hooks/index.ts | 34 +- .../components/common/hooks/useRootScope.ts | 22 + .../container/health-check.container.tsx | 22 +- .../components/health-check/health-check.tsx | 505 ------------------ .../health-check/lib/check-kibana-settings.ts | 36 -- .../health-check/lib/check-time-filter.ts | 43 -- public/components/health-check/lib/index.ts | 3 - .../services/check-fields.service.ts | 2 +- .../{lib => services}/check-max-buckets.ts | 16 +- .../services/check-pattern-support.service.ts | 2 +- .../services/check-pattern.service.ts | 6 +- .../services/check-setup.service.ts | 2 +- .../services/check-template.service.ts | 5 +- .../components/health-check/services/index.ts | 23 +- 15 files changed, 101 insertions(+), 652 deletions(-) create mode 100644 public/components/common/hooks/useRootScope.ts delete mode 100644 public/components/health-check/health-check.tsx delete mode 100644 public/components/health-check/lib/check-kibana-settings.ts delete mode 100644 public/components/health-check/lib/check-time-filter.ts delete mode 100644 public/components/health-check/lib/index.ts rename public/components/health-check/{lib => services}/check-max-buckets.ts (76%) diff --git a/public/components/common/hocs/index.ts b/public/components/common/hocs/index.ts index 55eae872df..aae04a0398 100644 --- a/public/components/common/hocs/index.ts +++ b/public/components/common/hocs/index.ts @@ -9,24 +9,14 @@ * * Find more information about this on the LICENSE file. */ -export { withWindowSize } from './withWindowSize'; - -export { withKibanaContext, withKibanaContextExtendsProps } from './withKibanaContext'; - -export { withUserPermissions, withUserPermissionsRequirements, withUserPermissionsPrivate } from './withUserPermissions'; - -export { withUserRoles, withUserRolesRequirements, withUserRolesPrivate } from './withUserRoles'; - -export { withUserAuthorizationPrompt} from './withUserAuthorization'; - -export { withGlobalBreadcrumb } from './withGlobalBreadcrumb'; - -export { withReduxProvider } from './withReduxProvider'; - -export { withGuard } from './withGuard'; - -export { withButtonOpenOnClick } from './withButtonOpenOnClick'; - -export { withAgentSupportModule } from './withAgentSupportModule'; - -export { withUserLogged } from './withUserLogged'; +export * from './withWindowSize'; +export * from './withKibanaContext'; +export * from './withUserPermissions'; +export * from './withUserRoles'; +export * from './withUserAuthorization'; +export * from './withGlobalBreadcrumb'; +export * from './withReduxProvider'; +export * from './withGuard'; +export * from './withButtonOpenOnClick'; +export * from './withAgentSupportModule'; +export * from './withUserLogged'; diff --git a/public/components/common/hooks/index.ts b/public/components/common/hooks/index.ts index 4d1599874d..27e864de72 100644 --- a/public/components/common/hooks/index.ts +++ b/public/components/common/hooks/index.ts @@ -10,26 +10,16 @@ * Find more information about this on the LICENSE file. */ -export { useFilterManager } from './use-filter-manager'; - -export { useIndexPattern } from './use-index-pattern'; - -export { useKbnLoadingIndicator } from './use-kbn-loading-indicator'; - -export { useQuery } from './use-query'; - -export { useTimeFilter } from './use-time-filter'; - -export { useWindowSize } from './useWindowSize'; - -export { useUserPermissions, useUserPermissionsRequirements, useUserPermissionsPrivate } from './useUserPermissions'; - -export { useUserRoles, useUserRolesRequirements, useUserRolesPrivate } from './useUserRoles'; - -export { useRefreshAngularDiscover } from './useResfreshAngularDiscover'; - -export { useAllowedAgents } from './useAllowedAgents'; - -export { useApiRequest } from './useApiRequest'; - +export * from './use-filter-manager'; +export * from './use-index-pattern'; +export * from './use-kbn-loading-indicator'; +export * from './use-query'; +export * from './use-time-filter'; +export * from './useWindowSize'; +export * from './useUserPermissions'; +export * from './useUserRoles'; +export * from './useResfreshAngularDiscover'; +export * from './useAllowedAgents'; +export * from './useApiRequest'; export * from './use-app-config'; +export * from './useRootScope'; diff --git a/public/components/common/hooks/useRootScope.ts b/public/components/common/hooks/useRootScope.ts new file mode 100644 index 0000000000..de6d0b9628 --- /dev/null +++ b/public/components/common/hooks/useRootScope.ts @@ -0,0 +1,22 @@ +/* + * Wazuh app - React hook to get the AngularJS $rootScope + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ +import { useEffect, useRef } from 'react'; +import { getAngularModule } from '../../../kibana-services'; + +export function useRootScope(){ + const refRootScope = useRef(); + useEffect(() => { + const app = getAngularModule(); + refRootScope.current = app.$injector.get('$rootScope'); + },[]); + return refRootScope.current; +}; diff --git a/public/components/health-check/container/health-check.container.tsx b/public/components/health-check/container/health-check.container.tsx index e30c0fb634..08a44ba6c6 100644 --- a/public/components/health-check/container/health-check.container.tsx +++ b/public/components/health-check/container/health-check.container.tsx @@ -20,16 +20,17 @@ import { } from '@elastic/eui'; import React, { Fragment, useState, useEffect, useRef } from 'react'; import { AppState, ErrorHandler } from '../../../react-services'; -import { useAppConfig } from '../../../components/common/hooks'; +import { useAppConfig, useRootScope } from '../../../components/common/hooks'; import { - checkPatternService, - checkTemplateService, checkApiService, - checkSetupService, checkFieldsService, checkKibanaSettings, checkKibanaSettingsTimeFilter, - checkPatternSupportService + checkKibanaSettingsMaxBuckets, + checkPatternService, + checkPatternSupportService, + checkSetupService, + checkTemplateService, } from '../services'; import { CheckResult } from '../components/check-result'; import { withReduxProvider } from '../../common/hocs'; @@ -87,12 +88,14 @@ function HealthCheckComponent() { const [checksReady, setChecksReady] = useState<{[key: string]: boolean}>({}); const appConfig = useAppConfig(); const checksInitiated = useRef(false); + const $rootScope = useRootScope(); useEffect(() => { if (appConfig.isReady && !checksInitiated.current) { checksInitiated.current = true; checkKibanaSettings(appConfig.data['checks.metaFields']); checkKibanaSettingsTimeFilter(appConfig.data['checks.timeFilter']); + checkKibanaSettingsMaxBuckets(appConfig.data['checks.maxBuckets']); AppState.setPatternSelector(appConfig.data['ip.selector']); } }, [appConfig]); @@ -100,7 +103,14 @@ function HealthCheckComponent() { useEffect(() => { // Redirect to app when all checks are ready Object.keys(checks) - .every(check => checksReady[check]) && (window.location.href = getHttp().basePath.prepend('/app/wazuh#/overview')); + .every(check => checksReady[check]) + && (() => setTimeout(() => { + const params = $rootScope.previousParams || {}; + const queryString = Object.keys(params).map(key => key + '=' + params[key]).join('&'); + const url = '/app/wazuh#' + ($rootScope.previousLocation || '') + '?' + queryString; + window.location.href = getHttp().basePath.prepend(url); + }, 300) + )() }, [checksReady]); const handleErrors = (checkID, errors, parsed) => { diff --git a/public/components/health-check/health-check.tsx b/public/components/health-check/health-check.tsx deleted file mode 100644 index 53aa528e16..0000000000 --- a/public/components/health-check/health-check.tsx +++ /dev/null @@ -1,505 +0,0 @@ -import React, { Component } from 'react'; -import { EuiLoadingSpinner, EuiDescriptionList, EuiIcon, EuiCallOut, EuiButtonIcon, EuiSpacer, EuiButton, EuiToolTip } from '@elastic/eui'; -import { AppState } from '../../react-services/app-state'; -import { PatternHandler } from '../../react-services/pattern-handler'; -import { getAngularModule, getToasts, getHttp, getDataPlugin } from '../../kibana-services'; -import { WazuhConfig } from '../../react-services/wazuh-config'; -import { GenericRequest } from '../../react-services/generic-request'; -import { ApiCheck } from '../../react-services/wz-api-check'; -import { WzRequest } from '../../react-services/wz-request'; -import { SavedObject } from '../../react-services/saved-objects'; -import { ErrorHandler } from '../../react-services/error-handler'; -import { WAZUH_ERROR_DAEMONS_NOT_READY, WAZUH_INDEX_TYPE_STATISTICS, WAZUH_INDEX_TYPE_MONITORING, HEALTH_CHECK } from '../../../common/constants'; -import { checkKibanaSettings, checkKibanaSettingsTimeFilter, checkKibanaSettingsMaxBuckets } from './lib'; -import store from '../../redux/store'; -import { updateWazuhNotReadyYet } from '../../redux/actions/appStateActions.js'; - -export class HealthCheck extends Component { - checkPatternCount = 0; - constructor(props) { - super(props); - this.state = { - checks: [], - results: [], - errors: [] - }; - } - async componentDidMount() { - const app = getAngularModule(); - this.$rootScope = app.$injector.get('$rootScope'); - this.load(); - } - - showToast = (color, title, text, time) => { - getToasts().add({ - color: color, - title: title, - text: text, - toastLifeTimeMs: time - }); - }; - - /** - * Manage an error - */ - handleError(error) { - let errors = this.state.errors; - errors.push(ErrorHandler.handle(error, 'Health Check', { silent: true })); - this.setState({ errors }); - } - - /** - * Sleep method - * @param time - */ - delay = time => new Promise(res => setTimeout(res, time)); - - async checkDefaultPattern(defaultPattern) { - if (defaultPattern) { - const patternData = await SavedObject.existsIndexPattern(defaultPattern); - patternData.status && getDataPlugin().indexPatterns.setDefault(defaultPattern, true); - } - } - - /** - * This validates a pattern - */ - async checkPatterns() { - this.checkPatternCount++; - if (this.checkPatternCount > 10) return Promise.reject('Error trying to check patterns.'); - try { - let patternTitle = ''; - let results = this.state.results; - let errors = this.state.errors; - const resultIndex = this.state.results.map(item => item.id).indexOf(2); - const currentPattern = AppState.getCurrentPattern(); - - if (this.state.checks.pattern) { - //get patters or create default - const patternList = await PatternHandler.getPatternList(HEALTH_CHECK); - // check selected pattern - const wazuhConfig = new WazuhConfig(); - const { pattern: defaultPattern } = wazuhConfig.getConfig(); - await this.checkDefaultPattern(defaultPattern); - - //check selected pattern - let patternData = currentPattern ? await SavedObject.existsIndexPattern(currentPattern) : false; - if (!patternData) patternData = {}; - patternTitle = patternData.title; - - if (!patternData.status) { - if (patternList.length) { - const indexPatternDefault = patternList.find((indexPattern) => indexPattern.title === defaultPattern); - indexPatternDefault && AppState.setCurrentPattern(indexPatternDefault.id); - await this.delay(3000); - return await this.checkPatterns(); - } else { - errors.push('The selected index-pattern is not present.'); - results[resultIndex].description = Error; - } - } else { - results[resultIndex].description = Ready; - } - this.setState({ results, errors }); - } - - if (this.state.checks.template) { - if (!patternTitle) { - var patternData = await SavedObject.existsIndexPattern(currentPattern); - patternTitle = patternData.title; - } - const i = results.map(item => item.id).indexOf(3); - const templateData = await GenericRequest.request( - 'GET', - `/elastic/template/${patternTitle}` - ); - if (!templateData.data.status) { - errors.push('No template found for the selected index-pattern.'); - results[i].description = Error; - } else { - results[i].description = Ready; - } - this.setState({ results, errors }); - } - return; - } catch (error) { - this.handleError(error); - } - } - - async trySetDefault() { - try { - const response = await GenericRequest.request('GET', '/hosts/apis'); - const hosts = response.data; - const errors = []; - const results = this.state.results; - const maxTries = 5; - let apiId = ''; - - if (hosts.length) { - for (let i = 0; i < hosts.length; i++) { - for (let tries = 0; tries < maxTries; tries++) { - await this.delay(3000); - try { - const API = await ApiCheck.checkApi(hosts[i], true); - if (API && API.data) { - apiId = hosts[i].id; - tries = maxTries; - i = hosts.length - } - } catch (err) { - if (tries) { - results[0].description = Retrying {'.'.repeat(tries) }; - results[1].description = Retrying {'.'.repeat(tries) }; - this.setState({ results }); - } else { - if (err.includes(WAZUH_ERROR_DAEMONS_NOT_READY)) { - const updateNotReadyYet = updateWazuhNotReadyYet(false); - store.dispatch(updateNotReadyYet); - } - errors.push(`Could not connect to API with id: ${hosts[i].id}: ${err.message || err}`); - } - } - } - if (apiId) return apiId; - } - - const updateNotReadyYet = updateWazuhNotReadyYet(false); - store.dispatch(updateNotReadyYet); - - if (errors.length) { - let err = this.state.errors; - errors.forEach(error => err.push(error)); - this.setState({ errors: err }) - return Promise.reject('No API available to connect.'); - } - } - } catch (err) { - return Promise.reject(`Error connecting to API: ${err}`); - } - } - - /** - * This attempts to reconnect with API - */ - reconnectWithAPI() { - let results = this.state.results; - results[0].description = Checking...; - results[1].description = Checking...; - getToasts().toasts$._value.forEach(toast => { - if (toast.text.includes('3000')) - getToasts().remove(toast.id); - }); - - const errors = this.state.errors.filter((error: string) => error.indexOf('API') < 0) - this.setState({ results, errors }); - this.checkApiConnection(); - } - - /** - * This attempts to connect with API - */ - async checkApiConnection() { - let results = this.state.results; - let errors = this.state.errors; - let apiChanged = false; - const buttonRestartApi =
Error - { - this.reconnectWithAPI()} - size="m" - aria-label="Next" - /> - } -
; - - try { - const currentApi = JSON.parse(AppState.getCurrentAPI() || '{}'); - if (this.state.checks.api && currentApi && currentApi.id) { - let data; - try { - data = await ApiCheck.checkStored(currentApi.id); - // Fix when the app endpoint replies with a valid response but the API is really down - if(data.data.data.apiIsDown){ - throw 'API is down' - }; - } catch (err) { - try { - const newApi = await this.trySetDefault(); - data = await ApiCheck.checkStored(newApi, true); - apiChanged = true; - } catch (err2) { - throw err2 - }; - } - if (apiChanged) { - this.showToast( - 'warning', - 'Selected Wazuh API has been updated', - '', - 3000 - ); - const api = ((data || {}).data || {}).data || {}; - const name = (api.cluster_info || {}).manager || false; - AppState.setCurrentAPI( - JSON.stringify({ name: name, id: api.id }) - ); - } - //update cluster info - const cluster_info = (((data || {}).data || {}).data || {}) - .cluster_info; - if (cluster_info) { - AppState.setClusterInfo(cluster_info); - } - const i = results.map(item => item.id).indexOf(0); - if (data === 3099) { - errors.push('Wazuh not ready yet.'); - results[i].description = Error;; - if (this.checks.setup) { - const i = results.map(item => item.id).indexOf(1); - results[i].description = Error; - } - this.setState({ results, errors }); - } else if (data.data.error || data.data.data.apiIsDown) { - errors.push(data.data.data.apiIsDown ? 'Wazuh API is down.' : `Error connecting to the API.${data.data.error && data.data.error.message ? ` ${data.data.error.message}` : ''}`); - results[i].description = buttonRestartApi; - results[i + 1].description = Error - this.setState({ results, errors }); - } else { - results[i].description = Ready; - this.setState({ results, errors }); - if (this.state.checks.setup) { - const versionData = await WzRequest.apiReq( - 'GET', - '//', - {} - ); - const apiVersion = versionData.data.data.api_version; - const setupData = await GenericRequest.request( - 'GET', - '/api/setup' - ); - if (!setupData.data.data['app-version']) { - errors.push('Error fetching app version'); - }; - if (!apiVersion) { - errors.push('Error fetching Wazuh API version'); - }; - const api = /v?(?\d+)\.(?\d+)\.(?\d+)/.exec(apiVersion); - const appSplit = setupData.data.data['app-version'].split('.'); - - const i = this.state.results.map(item => item.id).indexOf(1); - if (api.groups.version !== appSplit[0] || api.groups.minor !== appSplit[1]) { - errors.push( - 'API version mismatch. Expected v' + - setupData.data.data['app-version'] - ); - results[i].description = Error; - this.setState({ results, errors }); - } else { - results[i].description = Ready; - const updateNotReadyYet = updateWazuhNotReadyYet(false); - store.dispatch(updateNotReadyYet); - this.setState({ results, errors }); - } - } - } - } - return; - } catch (error) { - results[0].description = buttonRestartApi; - results[1].description = Error; - this.setState({ results }); - AppState.removeNavigation(); - if (error && error.data && error.data.code && error.data.code === 3002) { - return error; - } else { - this.handleError(error); - } - } - } - - /** - * This check if the pattern exist then create if not - * @param pattern string - */ - async checkSupportPattern(pattern, itemId, indexType) { - let results = this.state.results; - let errors = this.state.errors; - - const result = await SavedObject.existsIndexPattern(pattern); - if (!result.data) { - const toast = getToasts().addWarning(`${pattern} index pattern was not found and it will be created`) - const fields = await SavedObject.getIndicesFields(pattern, indexType); - try { - await SavedObject.createSavedObject( - 'index-pattern', - pattern, - { - attributes: { - title: pattern, - timeFieldName: 'timestamp' - } - }, - fields - ); - getToasts().remove(toast.id); - getToasts().addSuccess(`${pattern} index pattern created successfully`) - results[itemId].description = Ready; - this.setState({ results }); - } catch (error) { - getToasts().remove(toast.id); - errors.push(`Error trying to create ${pattern} index pattern: ${error.message}`); - results[itemId].description = Error; - this.setState({ results }); - } - } else { - results[itemId].description = Ready; - this.setState({ results }); - } - } - - /** - * On controller loads - */ - async load() { - try { - const wazuhConfig = new WazuhConfig(); - const configuration = wazuhConfig.getConfig(); - checkKibanaSettings(configuration['checks.metaFields']); - checkKibanaSettingsTimeFilter(configuration['checks.timeFilter']); - checkKibanaSettingsMaxBuckets(configuration['checks.maxBuckets']); - AppState.setPatternSelector(configuration['ip.selector']); - let checks = {}; - checks.pattern = configuration['checks.pattern']; - checks.template = configuration['checks.template']; - checks.api = configuration['checks.api']; - checks.setup = configuration['checks.setup']; - checks.fields = configuration['checks.fields']; - const results = [] - results.push( - { - id: 0, - title: 'Check Wazuh API connection', - description: checks.api ? Checking... : 'Disabled' - }, - { - id: 1, - title: 'Check for Wazuh API version', - description: checks.setup ? Checking... : 'Disabled' - }, - { - id: 2, - title: 'Check Elasticsearch index pattern', - description: checks.pattern ? Checking... : 'Disabled' - }, - { - id: 3, - title: 'Check Elasticsearch template', - description: checks.template ? Checking... : 'Disabled' - }, - { - id: 4, - title: 'Check index pattern fields', - description: checks.fields ? Checking... : 'Disabled' - }, - { - id: 5, - title: 'Check Monitoring index pattern', - description: Checking... - }, - { - id: 6, - title: 'Check Statistics index pattern', - description: Checking... - }, - ); - this.setState({ checks, results }, - async () => { - await Promise.all([ - this.checkPatterns(), - this.checkApiConnection(), - this.checkSupportPattern(configuration['wazuh.monitoring.pattern'], 5, WAZUH_INDEX_TYPE_MONITORING), - this.checkSupportPattern(`${configuration['cron.prefix']}-${configuration['cron.statistics.index.name']}-*`, 6, WAZUH_INDEX_TYPE_STATISTICS), - ]); - if (checks.fields) { - const i = results.map(item => item.id).indexOf(4); - try { - await PatternHandler.refreshIndexPattern(); - results[i].description = Ready; - this.setState({ results }); - } catch (error) { - results[i].description = Error; - this.setState({ results }, () => this.handleError(error)); - } - } - - if (!this.state.errors || !this.state.errors.length) { - setTimeout(() => { - const params = this.$rootScope.previousParams || {}; - var queryString = Object.keys(params).map(key => key + '=' + params[key]).join('&'); - const url = 'wazuh#' + (this.$rootScope.previousLocation || '') + '?' + queryString; - window.location.assign( - getHttp().basePath.prepend(url) - ); - return; - }, 300); - } - return; - }); - } catch (error) { - this.handleError(error); - } - } - - goAppSettings() { - window.location.href = '/app/wazuh#/settings'; - } - - render() { - const logo_url = getHttp().basePath.prepend('/plugins/wazuh/assets/icon_blue.svg'); - return ( -
- {!this.state.errors && ( - - )} - -
- -
- - {(this.state.errors || []).map(error => ( - <> - - - - - ))} - - {!!this.state.errors.length && ( - this.goAppSettings()}> - Go to App - - )} -
- ); - } -}; diff --git a/public/components/health-check/lib/check-kibana-settings.ts b/public/components/health-check/lib/check-kibana-settings.ts deleted file mode 100644 index c54bb937f6..0000000000 --- a/public/components/health-check/lib/check-kibana-settings.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AxiosResponse } from 'axios'; -import { GenericRequest } from '../../../react-services'; - -type userValue = {userValue: T} - -type kbnSettings = { - buildNum: userValue - metaFields?: userValue, -}; - -type responseKbnSettings = {settings: kbnSettings}; - -export function checkKibanaSettings (removeMetaFields: boolean) { - removeMetaFields && getKibanaSettings() - .then(checkMetafieldSetting) - .then(updateMetaFieldsSetting) - .catch(error => error !== 'Unable to update config' && console.log(error)); -} - -async function getKibanaSettings(): Promise { - const kibanaSettings:AxiosResponse = await GenericRequest.request('GET', '/api/kibana/settings'); - return kibanaSettings.data; -} - -async function checkMetafieldSetting({settings}: responseKbnSettings) { - const { metaFields } = settings; - return !!metaFields && !!metaFields.userValue.length; -} - -async function updateMetaFieldsSetting(isModified:boolean) { - return !isModified && await GenericRequest.request( - 'POST', - '/api/kibana/settings', - {"changes":{"metaFields":[]}} - ); -} \ No newline at end of file diff --git a/public/components/health-check/lib/check-time-filter.ts b/public/components/health-check/lib/check-time-filter.ts deleted file mode 100644 index 03b5fd08a6..0000000000 --- a/public/components/health-check/lib/check-time-filter.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { AxiosResponse } from 'axios'; -import { GenericRequest } from '../../../react-services'; -import { WAZUH_TIME_FILTER_DEFAULT } from '../../../../common/constants' -import { getDataPlugin } from '../../../kibana-services'; - -type userValue = { userValue: T } -type kbnSettings = { - buildNum: userValue - timeFilter?: userValue, -}; - - -type responseKbnSettings = { settings: kbnSettings }; - -export function checkKibanaSettingsTimeFilter(changeTimeDefaults: boolean) { - changeTimeDefaults && getKibanaSettings() - .then(checkTimeFilter) - .then(updateTimeFilterSetting) - .catch(error => error !== 'Unable to update config' && console.log(error)); -} - -async function getKibanaSettings(): Promise { - const kibanaSettings: AxiosResponse = await GenericRequest.request('GET', '/api/kibana/settings'); - return kibanaSettings.data; -} - -async function checkTimeFilter({ settings }: responseKbnSettings) { - if (!settings["timepicker:timeDefaults"]) { - return false; - } - - const timeFilter = settings["timepicker:timeDefaults"].userValue; - const timeFilterObject = JSON.parse(timeFilter); - return WAZUH_TIME_FILTER_DEFAULT.from == timeFilterObject.from && WAZUH_TIME_FILTER_DEFAULT.to == timeFilterObject.to; -} - -async function updateTimeFilterSetting(isModified: boolean) { - return !isModified && await GenericRequest.request( - 'POST', - '/api/kibana/settings', - { "changes": { "timepicker:timeDefaults": JSON.stringify(WAZUH_TIME_FILTER_DEFAULT) } } - ) && getDataPlugin().query.timefilter.timefilter.setTime(WAZUH_TIME_FILTER_DEFAULT); -} \ No newline at end of file diff --git a/public/components/health-check/lib/index.ts b/public/components/health-check/lib/index.ts deleted file mode 100644 index 77a9fa5d6f..0000000000 --- a/public/components/health-check/lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { checkKibanaSettings } from './check-kibana-settings'; -export { checkKibanaSettingsTimeFilter } from './check-time-filter'; -export { checkKibanaSettingsMaxBuckets } from './check-max-buckets'; diff --git a/public/components/health-check/services/check-fields.service.ts b/public/components/health-check/services/check-fields.service.ts index c144e66986..472cab3197 100644 --- a/public/components/health-check/services/check-fields.service.ts +++ b/public/components/health-check/services/check-fields.service.ts @@ -15,7 +15,7 @@ import { PatternHandler } from '../../../react-services'; export const checkFieldsService = async (): Promise<{ errors: string[] }> => { - let errors: string[] = []; + const errors: string[] = []; await PatternHandler.refreshIndexPattern(); return { errors }; diff --git a/public/components/health-check/lib/check-max-buckets.ts b/public/components/health-check/services/check-max-buckets.ts similarity index 76% rename from public/components/health-check/lib/check-max-buckets.ts rename to public/components/health-check/services/check-max-buckets.ts index 9c778fb019..cd852bae75 100644 --- a/public/components/health-check/lib/check-max-buckets.ts +++ b/public/components/health-check/services/check-max-buckets.ts @@ -1,7 +1,20 @@ +/* + * Wazuh app - Check Kibana max buckets setting Service + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + import { AxiosResponse } from 'axios'; import { GenericRequest } from '../../../react-services'; import { WAZUH_MAX_BUCKETS_DEFAULT } from '../../../../common/constants' -import { getDataPlugin } from '../../../kibana-services'; type userValue = { userValue: T } type kbnSettings = { @@ -10,7 +23,6 @@ type kbnSettings = { maxBuckets?: userValue }; - type responseKbnSettings = { settings: kbnSettings }; export function checkKibanaSettingsMaxBuckets(changeMaxBucketsDefaults: boolean) { diff --git a/public/components/health-check/services/check-pattern-support.service.ts b/public/components/health-check/services/check-pattern-support.service.ts index 80bc0d4f7c..a57f91cf84 100644 --- a/public/components/health-check/services/check-pattern-support.service.ts +++ b/public/components/health-check/services/check-pattern-support.service.ts @@ -15,7 +15,7 @@ import { SavedObject } from '../../../react-services'; import { getToasts } from '../../../kibana-services'; export const checkPatternSupportService = async (pattern: string, indexType : string): Promise<{ errors: string[] }> => { - let errors: string[] = []; + const errors: string[] = []; const result = await SavedObject.existsIndexPattern(pattern); if (!result.data) { const toast = getToasts().addWarning(`${pattern} index pattern was not found and it will be created`); diff --git a/public/components/health-check/services/check-pattern.service.ts b/public/components/health-check/services/check-pattern.service.ts index 1cfbff22b1..275675f8f2 100644 --- a/public/components/health-check/services/check-pattern.service.ts +++ b/public/components/health-check/services/check-pattern.service.ts @@ -17,15 +17,13 @@ import { HEALTH_CHECK } from '../../../../common/constants'; export const checkPatternService = async (appConfig): Promise<{ errors: string[] }> => { const patternId = AppState.getCurrentPattern(); - let errors: string[] = []; + const errors: string[] = []; const patternList = await PatternHandler.getPatternList(HEALTH_CHECK); const existsDefaultPattern = await SavedObject.existsIndexPattern(appConfig.data['pattern']); existsDefaultPattern.status && getDataPlugin().indexPatterns.setDefault(appConfig.data['pattern'], true); - let patternData = patternId ? await SavedObject.existsIndexPattern(patternId) : false; - - if (!patternData) patternData = {}; + const patternData = patternId ? (await SavedObject.existsIndexPattern(patternId)) || {} : {} ; if (!patternData.status) { if (patternList.length) { diff --git a/public/components/health-check/services/check-setup.service.ts b/public/components/health-check/services/check-setup.service.ts index e8d8ad8a3c..2a853f6ffc 100644 --- a/public/components/health-check/services/check-setup.service.ts +++ b/public/components/health-check/services/check-setup.service.ts @@ -15,7 +15,7 @@ import { AppState, GenericRequest, WzRequest } from '../../../react-services'; export const checkSetupService = async (): Promise<{ errors: string[] }> => { - let errors: string[] = []; + const errors: string[] = []; const currentApi = JSON.parse(AppState.getCurrentAPI() || '{}'); if (currentApi && currentApi.id) { const versionData = await WzRequest.apiReq('GET', '//', {}); diff --git a/public/components/health-check/services/check-template.service.ts b/public/components/health-check/services/check-template.service.ts index 9ffe268208..e8a21566d8 100644 --- a/public/components/health-check/services/check-template.service.ts +++ b/public/components/health-check/services/check-template.service.ts @@ -15,11 +15,10 @@ import { AppState, GenericRequest, SavedObject } from '../../../react-services'; export const checkTemplateService = async (): Promise<{ errors: string[] }> => { - let errors: string[] = []; + const errors: string[] = []; const patternId = AppState.getCurrentPattern(); let patternTitle = ''; - let patternData = patternId ? await SavedObject.existsIndexPattern(patternId) : false; - if (!patternData) patternData = {}; + const patternData = patternId ? (await SavedObject.existsIndexPattern(patternId)) || {} : {}; patternTitle = patternData.title; const templateData = await GenericRequest.request('GET', `/elastic/template/${patternTitle}`); diff --git a/public/components/health-check/services/index.ts b/public/components/health-check/services/index.ts index 620a622833..998aff8b31 100644 --- a/public/components/health-check/services/index.ts +++ b/public/components/health-check/services/index.ts @@ -1,8 +1,23 @@ -export * from './check-pattern.service'; -export * from './check-template.service'; -export * from './check-setup.service'; +/* + * Wazuh app - Health check services + * + * Copyright (C) 2015-2021 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + * + */ + export * from './check-api.service'; export * from './check-fields.service'; export * from './check-kibana-settings'; -export * from './check-time-filter'; +export * from './check-max-buckets'; +export * from './check-pattern.service'; export * from './check-pattern-support.service'; +export * from './check-template.service'; +export * from './check-time-filter'; +export * from './check-setup.service'; From eb4162f25d7ea5157bc1fc477bcda1995e30c2bf Mon Sep 17 00:00:00 2001 From: Toni <34042064+Desvelao@users.noreply.github.com> Date: Tue, 4 May 2021 17:27:58 +0200 Subject: [PATCH 13/13] changelog: Fix link of the change --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5766959859..0f3acfed61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to the Wazuh app project will be documented in this file. ## Wazuh v4.2.0 - Kibana 7.10.2 , 7.11.2 - Revision 4202 ### Changed -- Refactored the Health check component [#2682](https://github.com/wazuh/wazuh-kibana-app/issues/2682) +- Refactored the Health check component [#3197](https://github.com/wazuh/wazuh-kibana-app/pull/3197) ## Wazuh v4.2.0 - Kibana 7.10.2 , 7.11.2 - Revision 4201