diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts index ef2bfb4fd7..e50e643ce9 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source.ts @@ -11,6 +11,7 @@ import { } from '../../../../../../../src/plugins/data/public'; import { search } from '../../search-bar/search-bar-service'; import { PatternDataSourceFilterManager } from './pattern-data-source-filter-manager'; +import { useSelector } from 'react-redux'; export class PatternDataSource implements tDataSource { id: string; @@ -52,7 +53,9 @@ export class PatternDataSource implements tDataSource { ); const scripted = pattern.getScriptedFields().map(field => field.spec); pattern.fields.replaceAll([...fields, ...scripted]); - await this.patternService.updateSavedObject(pattern); + try { + await this.patternService.updateSavedObject(pattern); + } catch {} } else { throw new Error('Error selecting index pattern: pattern not found'); } diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx index c5c819eab2..6f0ed611c8 100644 --- a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx +++ b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx @@ -2,61 +2,32 @@ import React from 'react'; import { compose } from 'redux'; import { connect } from 'react-redux'; import { withGuardAsync } from '../../../../common/hocs'; -import { getSavedObjects } from '../../../../../kibana-services'; -import { SavedObject } from '../../../../../react-services'; -import { NOT_TIME_FIELD_NAME_INDEX_PATTERN } from '../../../../../../common/constants'; +import { + existsIndices, + existsIndexPattern, + createIndexPattern, +} from '../../../../../react-services'; import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; import { vulnerabilityDetection } from '../../../../../utils/applications'; import { LoadingSpinnerDataSource } from '../../../../common/loading/loading-spinner-data-source'; import NavigationService from '../../../../../react-services/navigation-service'; +import { HTTP_STATUS_CODES } from '../../../../../../common/constants'; const INDEX_PATTERN_CREATION_NO_INDEX = 'INDEX_PATTERN_CREATION_NO_INDEX'; -async function checkExistenceIndexPattern(indexPatternID: string) { - return await getSavedObjects().client.get('index-pattern', indexPatternID); -} - -async function checkExistenceIndices(indexPatternId: string) { - try { - const fields = await SavedObject.getIndicesFields(indexPatternId); - return { exist: true, fields }; - } catch (error) { - return { exist: false }; - } -} - -async function createIndexPattern(indexPattern, fields: any) { - try { - await SavedObject.createSavedObject( - 'index-pattern', - indexPattern, - { - attributes: { - title: indexPattern, - timeFieldName: NOT_TIME_FIELD_NAME_INDEX_PATTERN, - }, - }, - fields, - ); - await SavedObject.validateIndexPatternSavedObjectCanBeFound([indexPattern]); - } catch (error) { - return { error: error.message }; - } -} - export async function validateVulnerabilitiesStateDataSources({ vulnerabilitiesStatesindexPatternID: indexPatternID, }) { try { // Check the existence of related index pattern - const existIndexPattern = await checkExistenceIndexPattern(indexPatternID); - let indexPattern = existIndexPattern; + const existIndexPattern = await existsIndexPattern(indexPatternID); + const indexPattern = existIndexPattern; // If the idnex pattern does not exist, then check the existence of index - if (existIndexPattern?.error?.statusCode === 404) { + if (existIndexPattern?.error?.statusCode === HTTP_STATUS_CODES.NOT_FOUND) { // Check the existence of indices - const { exist, fields } = await checkExistenceIndices(indexPatternID); + const { exist, fields } = await existsIndices(indexPatternID); if (!exist) { return { diff --git a/plugins/main/public/controllers/management/components/management/statistics/prompt-statistics-no-indices.tsx b/plugins/main/public/controllers/management/components/management/statistics/prompt-statistics-no-indices.tsx index cce57b4702..04331d4d2a 100644 --- a/plugins/main/public/controllers/management/components/management/statistics/prompt-statistics-no-indices.tsx +++ b/plugins/main/public/controllers/management/components/management/statistics/prompt-statistics-no-indices.tsx @@ -10,23 +10,19 @@ * Find more information about this on the LICENSE file. */ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { EuiEmptyPrompt } from '@elastic/eui'; -import { StatisticsDataSource } from '../../../../../components/common/data-source/pattern/statistics'; -export const PromptStatisticsNoIndices = () => { - const [indexName, setIndexName] = useState(''); - - useEffect(() => { - const STATISTICS_PATTERN_IDENTIFIER = - StatisticsDataSource.getIdentifierDataSourcePattern(); - setIndexName(STATISTICS_PATTERN_IDENTIFIER); - }, []); - - return ( +export const PromptStatisticsNoIndices = ({ indexPatternID, existIndex }) => { + return !existIndex ? ( + {indexPatternID} indices were not found.} + /> + ) : ( {indexName} indices were not found.} + title={

There was a problem creating the index pattern.

} /> ); }; diff --git a/plugins/main/public/controllers/management/components/management/statistics/statistics-overview.js b/plugins/main/public/controllers/management/components/management/statistics/statistics-overview.js index 9594ca3856..bc63ca5e08 100644 --- a/plugins/main/public/controllers/management/components/management/statistics/statistics-overview.js +++ b/plugins/main/public/controllers/management/components/management/statistics/statistics-overview.js @@ -33,9 +33,11 @@ import { } from '../../../../../components/common/hocs'; import { PromptStatisticsDisabled } from './prompt-statistics-disabled'; import { PromptStatisticsNoIndices } from './prompt-statistics-no-indices'; -import { WzRequest } from '../../../../../react-services/wz-request'; import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; -import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; +import { + HTTP_STATUS_CODES, + UI_LOGGER_LEVELS, +} from '../../../../../../common/constants'; import { getErrorOrchestrator } from '../../../../../react-services/common-services'; import { getCore } from '../../../../../kibana-services'; import { appSettings, statistics } from '../../../../../utils/applications'; @@ -43,6 +45,12 @@ import { RedirectAppLinks } from '../../../../../../../../src/plugins/opensearch import { DashboardTabsPanels } from '../../../../../components/overview/server-management-statistics/dashboards/dashboardTabsPanels'; import { connect } from 'react-redux'; import NavigationService from '../../../../../react-services/navigation-service'; +import { + existsIndices, + existsIndexPattern, + createIndexPattern, +} from '../../../../../react-services'; +import { StatisticsDataSource } from '../../../../../components/common/data-source/pattern/statistics'; export class WzStatisticsOverview extends Component { _isMounted = false; @@ -222,6 +230,7 @@ const mapStateToProps = state => ({ statisticsEnabled: state.appConfig.data?.['cron.statistics.status'], configurationUIEditable: state.appConfig.data?.['configuration.ui_api_editable'], + statisticsIndexPatternID: `${state.appConfig.data['cron.prefix']}-${state.appConfig.data['cron.statistics.index.name']}*`, }); export default compose( @@ -235,14 +244,34 @@ export default compose( return !props.statisticsEnabled; }, PromptStatisticsDisabled), )(props => { - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [existStatisticsIndices, setExistStatisticsIndices] = useState(false); + const [existStatisticsIndexPattern, setExistStatisticsIndexPattern] = + useState(false); + const indexPatternID = StatisticsDataSource.getIdentifierDataSourcePattern(); useEffect(() => { const fetchData = async () => { try { + // Check the existence of related index pattern + const existIndexPattern = await existsIndexPattern(indexPatternID); + const { exist, fields } = await existsIndices(indexPatternID); setLoading(true); - const data = await WzRequest.genericReq('GET', '/elastic/statistics'); - setExistStatisticsIndices(data.data); + + if (exist) { + setExistStatisticsIndices(true); + if (!existIndexPattern) { + // If some index match the index pattern, then create the index pattern + const resultCreateIndexPattern = await createIndexPattern( + indexPatternID, + fields, + ); + if (resultCreateIndexPattern?.error) { + setLoading(false); + return; + } + } + setExistStatisticsIndexPattern(true); + } } catch (error) { setLoading(false); const options = { @@ -265,9 +294,12 @@ export default compose( if (loading) { return ; } - return existStatisticsIndices ? ( + return existStatisticsIndices && existStatisticsIndexPattern ? ( ) : ( - + ); }); diff --git a/plugins/main/public/react-services/check-index.ts b/plugins/main/public/react-services/check-index.ts new file mode 100644 index 0000000000..bbb687a284 --- /dev/null +++ b/plugins/main/public/react-services/check-index.ts @@ -0,0 +1,35 @@ +import { SavedObject } from '.'; +import { getSavedObjects } from '../kibana-services'; +import { NOT_TIME_FIELD_NAME_INDEX_PATTERN } from '../../common/constants'; + +export async function existsIndices(indexPatternId: string) { + try { + const fields = await SavedObject.getIndicesFields(indexPatternId); + return { exist: true, fields }; + } catch (error) { + return { exist: false }; + } +} + +export async function existsIndexPattern(indexPatternID: string) { + return await getSavedObjects().client.get('index-pattern', indexPatternID); +} + +export async function createIndexPattern(indexPattern, fields: any) { + try { + await SavedObject.createSavedObject( + 'index-pattern', + indexPattern, + { + attributes: { + title: indexPattern, + timeFieldName: NOT_TIME_FIELD_NAME_INDEX_PATTERN, + }, + }, + fields, + ); + await SavedObject.validateIndexPatternSavedObjectCanBeFound([indexPattern]); + } catch (error) { + return { error: error.message }; + } +} diff --git a/plugins/main/public/react-services/index.ts b/plugins/main/public/react-services/index.ts index b09a1645a0..8c4e47644b 100644 --- a/plugins/main/public/react-services/index.ts +++ b/plugins/main/public/react-services/index.ts @@ -21,3 +21,4 @@ export * from './wz-security-opensearch-dashboards-security'; export * from './wz-user-permissions'; export * from './query-config'; export * from './elastic_helpers'; +export * from './check-index'; diff --git a/plugins/main/server/controllers/wazuh-elastic.ts b/plugins/main/server/controllers/wazuh-elastic.ts index b8e13d6bd9..ad0af76ee0 100644 --- a/plugins/main/server/controllers/wazuh-elastic.ts +++ b/plugins/main/server/controllers/wazuh-elastic.ts @@ -681,29 +681,6 @@ export class WazuhElasticCtrl { } } - // Check if there are indices for Statistics - async existStatisticsIndices( - context: RequestHandlerContext, - request: OpenSearchDashboardsRequest, - response: OpenSearchDashboardsResponseFactory, - ) { - try { - const config = await context.wazuh_core.configuration.get(); - const statisticsPattern = `${config['cron.prefix']}-${config['cron.statistics.index.name']}*`; - const existIndex = - await context.core.opensearch.client.asCurrentUser.indices.exists({ - index: statisticsPattern, - allow_no_indices: false, - }); - return response.ok({ - body: existIndex.body, - }); - } catch (error) { - context.wazuh.logger.error(error.message || error); - return ErrorResponse(error.message || error, 1000, 500, response); - } - } - getErrorDetails(error) { const statusCode = error?.meta?.statusCode || 500; let errorMessage = error.message; diff --git a/plugins/main/server/routes/wazuh-elastic.test.ts b/plugins/main/server/routes/wazuh-elastic.test.ts index cdc74a0431..7659f33b2b 100644 --- a/plugins/main/server/routes/wazuh-elastic.test.ts +++ b/plugins/main/server/routes/wazuh-elastic.test.ts @@ -3,10 +3,19 @@ import axios from 'axios'; import { PLUGIN_PLATFORM_REQUEST_HEADERS } from '../../common/constants'; -function buildAxiosOptions(method: string, path: string, data: any = {}, headers: any = {}) { +function buildAxiosOptions( + method: string, + path: string, + data: any = {}, + headers: any = {}, +) { return { method: method, - headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json', ...headers }, + headers: { + ...PLUGIN_PLATFORM_REQUEST_HEADERS, + 'content-type': 'application/json', + ...headers, + }, url: `http://localhost:5601${path}`, data: data, }; @@ -15,11 +24,16 @@ function buildAxiosOptions(method: string, path: string, data: any = {}, headers describe.skip('Wazuh Elastic', () => { describe('Wazuh API - /elastic/security/current-platform', () => { test('[200] Returns the current security platform as string or boolean', () => { - const options = buildAxiosOptions('get', '/elastic/security/current-platform'); - return axios(options).then((response) => { + const options = buildAxiosOptions( + 'get', + '/elastic/security/current-platform', + ); + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data).toBe('object'); - expect(['string', 'boolean'].includes(typeof response.data.platform)).toBe(true); + expect( + ['string', 'boolean'].includes(typeof response.data.platform), + ).toBe(true); }); }); }); @@ -58,8 +72,11 @@ describe.skip('Wazuh Elastic', () => { describe('Wazuh API - /elastic/template/{pattern}', () => { test('[200] Check if there is some template with the pattern', () => { - const options = buildAxiosOptions('get', '/elastic/template/wazuh-alerts-*'); - return axios(options).then((response) => { + const options = buildAxiosOptions( + 'get', + '/elastic/template/wazuh-alerts-*', + ); + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.status).toBe('boolean'); expect(typeof response.data.data).toBe('string'); @@ -80,7 +97,7 @@ describe.skip('Wazuh Elastic', () => { describe('Wazuh API - /elastic/samplealerts', () => { test('[200] Check if there an sample data indices', () => { const options = buildAxiosOptions('get', '/elastic/samplealerts'); - return axios(options).then((response) => { + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.sampleAlertsInstalled).toBe('boolean'); }); @@ -89,8 +106,11 @@ describe.skip('Wazuh Elastic', () => { describe('Wazuh API - /elastic/samplealerts/{category}', () => { test('[200] Check if there an sample data index of Security category', () => { - const options = buildAxiosOptions('get', '/elastic/samplealerts/security'); - return axios(options).then((response) => { + const options = buildAxiosOptions( + 'get', + '/elastic/samplealerts/security', + ); + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.exists).toBe('boolean'); @@ -98,8 +118,11 @@ describe.skip('Wazuh Elastic', () => { }); test('[200] Check if there an sample data index of Audit and Policy monitoring category', () => { - const options = buildAxiosOptions('get', '/elastic/samplealerts/auditing-policy-monitoring'); - return axios(options).then((response) => { + const options = buildAxiosOptions( + 'get', + '/elastic/samplealerts/auditing-policy-monitoring', + ); + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.exists).toBe('boolean'); @@ -107,8 +130,11 @@ describe.skip('Wazuh Elastic', () => { }); test('[200] Check if there an sample data index of Theard detection category', () => { - const options = buildAxiosOptions('get', '/elastic/samplealerts/threat-detection'); - return axios(options).then((response) => { + const options = buildAxiosOptions( + 'get', + '/elastic/samplealerts/threat-detection', + ); + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.exists).toBe('boolean'); @@ -123,11 +149,11 @@ describe.skip('Wazuh Elastic', () => { idHost: 'default', }); return axios(optionsAuthenticate) - .then((response) => { + .then(response => { userToken = response.data.token; return response.data.token; }) - .catch((error) => {}); + .catch(error => {}); }); test('[200] Create sample alers of Security category', () => { @@ -137,9 +163,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken};wz-api=default;`, - } + }, ); - return axios(options).then((response) => { + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.alertCount).toBe('number'); @@ -153,9 +179,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken};wz-api=default;`, - } + }, ); - return axios(options).then((response) => { + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.alertCount).toBe('number'); @@ -169,9 +195,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken};wz-api=default;`, - } + }, ); - return axios(options).then((response) => { + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.alertCount).toBe('number'); @@ -185,9 +211,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-api=default;`, - } + }, ); - return axios(options).catch((error) => { + return axios(options).catch(error => { expect(error.response.status).toBe(401); }); }); @@ -199,9 +225,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken};`, - } + }, ); - return axios(options).catch((error) => { + return axios(options).catch(error => { expect(error.response.status).toBe(401); }); }); @@ -213,9 +239,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken};wz-api=default;`, - } + }, ); - return axios(options).then((response) => { + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.result).toBe('string'); @@ -229,9 +255,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken};wz-api=default;`, - } + }, ); - return axios(options).then((response) => { + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.result).toBe('string'); @@ -245,9 +271,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken};wz-api=default;`, - } + }, ); - return axios(options).then((response) => { + return axios(options).then(response => { expect(response.status).toBe(200); expect(typeof response.data.index).toBe('string'); expect(typeof response.data.result).toBe('string'); @@ -261,9 +287,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-api=default;`, - } + }, ); - return axios(options).catch((error) => { + return axios(options).catch(error => { expect(error.response.status).toBe(401); }); }); @@ -275,9 +301,9 @@ describe.skip('Wazuh Elastic', () => { {}, { cookie: `wz-token=${userToken}`, - } + }, ); - return axios(options).catch((error) => { + return axios(options).catch(error => { expect(error.response.status).toBe(401); }); }); @@ -294,14 +320,4 @@ describe.skip('Wazuh Elastic', () => { // }); // }); // }); - - describe('Wazuh API - /elastic/statistics', () => { - test('[200] Check if there an sample data index of Security category', () => { - const options = buildAxiosOptions('get', '/elastic/statistics'); - return axios(options).then((response) => { - expect(response.status).toBe(200); - expect(typeof response.data).toBe('boolean'); - }); - }); - }); }); diff --git a/plugins/main/server/routes/wazuh-elastic.ts b/plugins/main/server/routes/wazuh-elastic.ts index 3f4ea98450..68fe925007 100644 --- a/plugins/main/server/routes/wazuh-elastic.ts +++ b/plugins/main/server/routes/wazuh-elastic.ts @@ -130,13 +130,4 @@ export function WazuhElasticRoutes(router: IRouter) { async (context, request, response) => ctrl.alerts(context, request, response), ); - - router.get( - { - path: '/elastic/statistics', - validate: false, - }, - async (context, request, response) => - ctrl.existStatisticsIndices(context, request, response), - ); }