From 30faf2f006bbbfca202f90db32fee25944a7e3ad Mon Sep 17 00:00:00 2001 From: pablomarga Date: Tue, 6 Jul 2021 15:39:57 +0200 Subject: [PATCH 1/3] Techniques and mitre --- .../compliance-table/compliance-table.tsx | 276 +++--- .../components/overview/metrics/metrics.tsx | 708 +++++++++----- .../mitre/components/tactics/tactics.tsx | 253 ++--- .../flyout-technique/flyout-technique.tsx | 18 +- .../components/techniques/techniques.tsx | 888 ++++++++++-------- public/components/overview/mitre/mitre.tsx | 26 +- 6 files changed, 1278 insertions(+), 891 deletions(-) diff --git a/public/components/overview/compliance-table/compliance-table.tsx b/public/components/overview/compliance-table/compliance-table.tsx index 4e62a70fc7..2f3430708e 100644 --- a/public/components/overview/compliance-table/compliance-table.tsx +++ b/public/components/overview/compliance-table/compliance-table.tsx @@ -9,12 +9,8 @@ * * Find more information about this on the LICENSE file. */ -import React, { Component } from 'react' -import { - EuiPanel, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; +import React, { Component } from 'react'; +import { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SearchBar, FilterManager } from '../../../../../../src/plugins/data/public/'; import { I18nProvider } from '@kbn/i18n/react'; @@ -29,28 +25,30 @@ import { nistRequirementsFile } from '../../../../common/compliance-requirements import { tscRequirementsFile } from '../../../../common/compliance-requirements/tsc-requirements'; import { KbnSearchBar } from '../../kbn-search-bar'; import { getDataPlugin } from '../../../kibana-services'; - +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; export class ComplianceTable extends Component { _isMount = false; timefilter: { - getTime(): any - setTime(time: any): void - _history: { history: { items: { from: string, to: string }[] } } + getTime(): any; + setTime(time: any): void; + _history: { history: { items: { from: string; to: string }[] } }; }; KibanaServices: { [key: string]: any }; filterManager: FilterManager; indexPattern: any; state: { - selectedRequirement: string, - flyoutOn: boolean, - filterParams: IFilterParams, - complianceObject: object, - descriptions: object, - loadingAlerts: boolean - selectedRequirements: object, - } + selectedRequirement: string; + flyoutOn: boolean; + filterParams: IFilterParams; + complianceObject: object; + descriptions: object; + loadingAlerts: boolean; + selectedRequirements: object; + }; props: any; @@ -60,7 +58,7 @@ export class ComplianceTable extends Component { this.filterManager = this.KibanaServices.filterManager; this.timefilter = this.KibanaServices.timefilter.timefilter; this.state = { - selectedRequirement: "", + selectedRequirement: '', flyoutOn: true, complianceObject: {}, descriptions: {}, @@ -71,7 +69,7 @@ export class ComplianceTable extends Component { query: { language: 'kuery', query: '' }, time: this.timefilter.getTime(), }, - } + }; this.onChangeSelectedRequirements.bind(this); this.onQuerySubmit.bind(this); @@ -81,7 +79,7 @@ export class ComplianceTable extends Component { async componentDidMount() { this._isMount = true; this.filtersSubscriber = this.filterManager.getUpdates$().subscribe(() => { - this.onFiltersUpdated(this.filterManager.getFilters()) + this.onFiltersUpdated(this.filterManager.getFilters()); }); this.indexPattern = await getIndexPattern(); this.buildComplianceObject(); @@ -91,7 +89,6 @@ export class ComplianceTable extends Component { this.filtersSubscriber.unsubscribe(); } - buildComplianceObject() { try { let complianceRequirements = {}; @@ -99,8 +96,8 @@ export class ComplianceTable extends Component { let selectedRequirements = {}; // all enabled by default if (this.props.section === 'pci') { descriptions = pciRequirementsFile; - Object.keys(pciRequirementsFile).forEach(item => { - const currentRequirement = item.split(".")[0]; + Object.keys(pciRequirementsFile).forEach((item) => { + const currentRequirement = item.split('.')[0]; if (complianceRequirements[currentRequirement]) { complianceRequirements[currentRequirement].push(item); } else { @@ -112,8 +109,8 @@ export class ComplianceTable extends Component { } if (this.props.section === 'gdpr') { descriptions = gdprRequirementsFile; - Object.keys(gdprRequirementsFile).forEach(item => { - const currentRequirement = item.split("_")[0]; + Object.keys(gdprRequirementsFile).forEach((item) => { + const currentRequirement = item.split('_')[0]; if (complianceRequirements[currentRequirement]) { complianceRequirements[currentRequirement].push(item); } else { @@ -121,13 +118,14 @@ export class ComplianceTable extends Component { complianceRequirements[currentRequirement] = []; complianceRequirements[currentRequirement].push(item); } - }); //forEach + }); //forEach } if (this.props.section === 'hipaa') { descriptions = hipaaRequirementsFile; - Object.keys(hipaaRequirementsFile).forEach(item => { - const currentRequirement = item.split(".")[0] + "." + item.split(".")[1] + "." + item.split(".")[2]; + Object.keys(hipaaRequirementsFile).forEach((item) => { + const currentRequirement = + item.split('.')[0] + '.' + item.split('.')[1] + '.' + item.split('.')[2]; if (complianceRequirements[currentRequirement]) { complianceRequirements[currentRequirement].push(item); } else { @@ -135,13 +133,13 @@ export class ComplianceTable extends Component { complianceRequirements[currentRequirement] = []; complianceRequirements[currentRequirement].push(item); } - }); //forEach + }); //forEach } if (this.props.section === 'nist') { descriptions = nistRequirementsFile; - Object.keys(nistRequirementsFile).forEach(item => { - const currentRequirement = item.split(".")[0]; + Object.keys(nistRequirementsFile).forEach((item) => { + const currentRequirement = item.split('.')[0]; if (complianceRequirements[currentRequirement]) { complianceRequirements[currentRequirement].push(item); } else { @@ -149,12 +147,12 @@ export class ComplianceTable extends Component { complianceRequirements[currentRequirement] = []; complianceRequirements[currentRequirement].push(item); } - }); //forEach + }); //forEach } if (this.props.section === 'tsc') { descriptions = tscRequirementsFile; - Object.keys(tscRequirementsFile).forEach(item => { - const currentRequirement = item.split(".")[0]; + Object.keys(tscRequirementsFile).forEach((item) => { + const currentRequirement = item.split('.')[0]; if (complianceRequirements[currentRequirement]) { complianceRequirements[currentRequirement].push(item); } else { @@ -162,37 +160,47 @@ export class ComplianceTable extends Component { complianceRequirements[currentRequirement] = []; complianceRequirements[currentRequirement].push(item); } - }); //forEach + }); //forEach } - this._isMount && this.setState({ complianceObject: complianceRequirements, selectedRequirements, descriptions }, () => this.getRequirementsCount()); - } catch (err) { - // TODO ADD showToast - /*this.showToast( - 'danger', - 'Error', - `Compliance (${this.props.section}) data could not be fetched: ${err}`, - 3000 - );*/ + this._isMount && + this.setState( + { complianceObject: complianceRequirements, selectedRequirements, descriptions }, + () => this.getRequirementsCount() + ); + } catch (error) { + const options = { + context: `${ComplianceTable.name}.buildComplianceObject`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Compliance (${this.props.section}) data could not be fetched`, + }, + }; + getErrorOrchestrator().handleError(options); } } onChangeSelectedRequirements = (selectedRequirements) => { this.setState({ selectedRequirements }); - } + }; - onQuerySubmit = (payload: { dateRange: TimeRange, query: Query | undefined }) => { + onQuerySubmit = (payload: { dateRange: TimeRange; query: Query | undefined }) => { const { dateRange, query } = payload; const { filters } = this.state.filterParams; - const filterParams:IFilterParams = { time: dateRange, filters, query}; + const filterParams: IFilterParams = { time: dateRange, filters, query }; this.setState({ filterParams, loadingAlerts: true }); - } + }; onFiltersUpdated = (filters: []) => { - const { time, query} = this.state.filterParams; - const filterParams = {time, query, filters}; + const { time, query } = this.state.filterParams; + const filterParams = { time, query, filters }; this.setState({ filterParams, loadingAlerts: true }); - } + }; async componentDidUpdate(prevProps) { const { filterParams, loadingAlerts } = this.state; @@ -204,52 +212,58 @@ export class ComplianceTable extends Component { async getRequirementsCount() { try { const { filterParams } = this.state; - if (!this.indexPattern) { return; } - let fieldAgg = ""; - if (this.props.section === "pci") - fieldAgg = "rule.pci_dss"; - if (this.props.section === "gdpr") - fieldAgg = "rule.gdpr"; - if (this.props.section === "hipaa") - fieldAgg = "rule.hipaa"; - if (this.props.section === "nist") - fieldAgg = "rule.nist_800_53"; - if (this.props.section === "tsc") - fieldAgg = "rule.tsc"; + if (!this.indexPattern) { + return; + } + let fieldAgg = ''; + if (this.props.section === 'pci') fieldAgg = 'rule.pci_dss'; + if (this.props.section === 'gdpr') fieldAgg = 'rule.gdpr'; + if (this.props.section === 'hipaa') fieldAgg = 'rule.hipaa'; + if (this.props.section === 'nist') fieldAgg = 'rule.nist_800_53'; + if (this.props.section === 'tsc') fieldAgg = 'rule.tsc'; const aggs = { tactics: { terms: { field: fieldAgg, size: 100, - } - } - } + }, + }, + }; // TODO: use `status` and `statusText` to show errors // @ts-ignore - const { data, status, statusText, } = await getElasticAlerts(this.indexPattern, filterParams, aggs); + const { data, status, statusText } = await getElasticAlerts( + this.indexPattern, + filterParams, + aggs + ); const { buckets } = data.aggregations.tactics; /*if(firstTime){ this.initTactics(buckets); // top tactics are checked on component mount }*/ - this._isMount && this.setState({ requirementsCount: buckets, loadingAlerts: false, firstTime: false }); - - } catch (err) { - /* this.showToast( - 'danger', - 'Error', - `Mitre alerts could not be fetched: ${err}`, - 3000 - );*/ - this.setState({ loadingAlerts: false }) + this._isMount && + this.setState({ requirementsCount: buckets, loadingAlerts: false, firstTime: false }); + } catch (error) { + const options = { + context: `${ComplianceTable.name}.buildComplianceObject`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Mitre alerts could not be fetched:`, + }, + }; + getErrorOrchestrator().handleError(options); + this.setState({ loadingAlerts: false }); } - } - onChangeFlyout = (flyoutOn) => { this.setState({ flyoutOn }); - } + }; closeFlyout() { this.setState({ flyoutOn: false }); @@ -258,55 +272,63 @@ export class ComplianceTable extends Component { showFlyout(requirement) { this.setState({ selectedRequirement: requirement, - flyoutOn: true - }) + flyoutOn: true, + }); } - - render() { const { complianceObject, loadingAlerts } = this.state; - return (
- - -
- -
-
-
- - - - - {!!Object.keys(complianceObject).length && this.state.filterParams.time.from !== "init" && - - - - - - this.props.onSelectedTabChanged(id)} - {...this.state} /> - - - } - - - - - -
- ) + return ( +
+ + +
+ +
+
+
+ + + + + {!!Object.keys(complianceObject).length && + this.state.filterParams.time.from !== 'init' && ( + + + + + + this.props.onSelectedTabChanged(id)} + {...this.state} + /> + + + )} + + + +
+ ); } } - diff --git a/public/components/overview/metrics/metrics.tsx b/public/components/overview/metrics/metrics.tsx index 939bc5da9b..ec26d6210f 100644 --- a/public/components/overview/metrics/metrics.tsx +++ b/public/components/overview/metrics/metrics.tsx @@ -9,307 +9,539 @@ * * Find more information about this on the LICENSE file. */ -import React, { Component } from 'react' -import { - EuiStat, - EuiFlexGroup, - EuiFlexItem, - EuiToolTip, -} from '@elastic/eui'; +import React, { Component } from 'react'; +import { EuiStat, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { FilterManager } from '../../../../../../src/plugins/data/public/'; -import { buildRangeFilter, buildPhrasesFilter,buildPhraseFilter, buildExistsFilter} from '../../../../../../src/plugins/data/common'; +import { + buildRangeFilter, + buildPhrasesFilter, + buildPhraseFilter, + buildExistsFilter, +} from '../../../../../../src/plugins/data/common'; //@ts-ignore import { getElasticAlerts, getIndexPattern } from '../mitre/lib'; -import { ModulesHelper } from '../../common/modules/modules-helper' +import { ModulesHelper } from '../../common/modules/modules-helper'; import { getDataPlugin } from '../../../kibana-services'; import { withAllowedAgents } from '../../common/hocs/withAllowedAgents'; import { formatUIDate } from '../../../react-services'; +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; -export const Metrics = withAllowedAgents(class Metrics extends Component { - _isMount = false; - timefilter: { - getTime(): any - setTime(time: any): void - _history: { history: { items: { from: string, to: string }[] } } - }; +export const Metrics = withAllowedAgents( + class Metrics extends Component { + _isMount = false; + timefilter: { + getTime(): any; + setTime(time: any): void; + _history: { history: { items: { from: string; to: string }[] } }; + }; - KibanaServices: { [key: string]: any }; - filterManager: FilterManager; - indexPattern: any; - state: { - resultState: string, - results: object, - metricsOnClicks: object, - loading: boolean, - filterParams: object, - } - metricsList: object; + KibanaServices: { [key: string]: any }; + filterManager: FilterManager; + indexPattern: any; + state: { + resultState: string; + results: object; + metricsOnClicks: object; + loading: boolean; + filterParams: object; + }; + metricsList: object; - props: any; + props: any; - constructor(props) { - super(props); - this.KibanaServices = getDataPlugin().query; - this.filterManager = this.KibanaServices.filterManager; - this.timefilter = this.KibanaServices.timefilter.timefilter; - this.state = { - resultState: "", - results: {}, - metricsOnClicks: {}, - loading: true, - filterParams: { - filters: [], - query: { language: 'kuery', query: '' }, - time: {from: 'init', to: 'init'}, - }, - } - this.modulesHelper = ModulesHelper; - this.stats = <>; + constructor(props) { + super(props); + this.KibanaServices = getDataPlugin().query; + this.filterManager = this.KibanaServices.filterManager; + this.timefilter = this.KibanaServices.timefilter.timefilter; + this.state = { + resultState: '', + results: {}, + metricsOnClicks: {}, + loading: true, + filterParams: { + filters: [], + query: { language: 'kuery', query: '' }, + time: { from: 'init', to: 'init' }, + }, + }; + this.modulesHelper = ModulesHelper; + this.stats = <>; - this.metricsList = { - general: [ - { name: "Total", type: "total" }, - { name: "Level 12 or above alerts", type: "range", gte: "12", lt: null, field: "rule.level", color: "danger"}, //null = infinite - { name: "Authentication failure", type: "phrases", values: ["win_authentication_failed", "authentication_failed", "authentication_failures"], field: "rule.groups",color: "danger"}, - { name: "Authentication success", type: "phrase", value: "authentication_success", field: "rule.groups", color: "secondary"}, - ], - vuls: [ - { name: "Critical Severity Alerts", type: "phrase", value: "Critical", field: "data.vulnerability.severity", color: "danger"}, - { name: "High Severity Alerts", type: "phrase", value: "High", field: "data.vulnerability.severity"}, - { name: "Medium Severity Alerts", type: "phrase", value: "Medium", field: "data.vulnerability.severity", color: "secondary"}, - { name: "Low Severity Alerts", type: "phrase", value: "Low", field: "data.vulnerability.severity", color: "subdued"}, - ], - virustotal: [ - { name: "Total malicious", type: "phrase", value: "1", field: "data.virustotal.malicious", color: "danger"}, - { name: "Total positives", type: "phrase", value: "0", negate: true, field: "data.virustotal.positives", color: "secondary"}, - { name: "Total", type: "total"}, - ], - osquery: [ - { name: "Agents reporting", type: "unique-count", field: "agent.id"}, - ], - ciscat: [ - { name: "Last scan not checked", type: "custom", filter: { phrase: "ciscat", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.cis.notchecked" } } } } }, color: "subdued"}, - { name: "Last scan pass", type: "custom", filter: { phrase: "ciscat", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.cis.pass" } } } } }, color: "secondary"}, - { name: "Last scan score", type: "custom", filter: { phrase: "ciscat", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.cis.score" } } } } }}, - { name: "Last scan date", type: "custom", filter: { phrase: "ciscat", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.cis.timestamp" } } } } }, color: "secondary", transformValue:formatUIDate}, - { name: "Last scan errors", type: "custom", filter: { phrase: "ciscat", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.cis.error" } } } } }, color: "danger"}, - { name: "Last scan fails", type: "custom", filter: { phrase: "ciscat", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.cis.fail" } } } } }, color: "danger"}, - { name: "Last scan unknown", type: "custom", filter: { phrase: "ciscat", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.cis.unknown" } } } } }, color: "subdued"}, - ], - oscap: [ - { name: "Last scan score", type: "custom", filter: { phrase: "oscap-report", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "timestamp", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.oscap.scan.score" } } } }}}, - { name: "Highest scan score", type: "custom", filter: { phrase: "oscap-report", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "data.oscap.scan.score", "order": { "_term": "desc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.oscap.scan.score" } } } }}, color: "secondary"}, - { name: "Lowest scan score", type: "custom", filter: { phrase: "oscap-report", field:"rule.groups"} , agg: { "customAggResult": { "terms": { "field": "data.oscap.scan.score", "order": { "_term": "asc" }, "size": 1 }, "aggs": { "aggResult": { "terms": { "field": "data.oscap.scan.score" } } } }}, color: "danger"}, - ] + this.metricsList = { + general: [ + { name: 'Total', type: 'total' }, + { + name: 'Level 12 or above alerts', + type: 'range', + gte: '12', + lt: null, + field: 'rule.level', + color: 'danger', + }, //null = infinite + { + name: 'Authentication failure', + type: 'phrases', + values: [ + 'win_authentication_failed', + 'authentication_failed', + 'authentication_failures', + ], + field: 'rule.groups', + color: 'danger', + }, + { + name: 'Authentication success', + type: 'phrase', + value: 'authentication_success', + field: 'rule.groups', + color: 'secondary', + }, + ], + vuls: [ + { + name: 'Critical Severity Alerts', + type: 'phrase', + value: 'Critical', + field: 'data.vulnerability.severity', + color: 'danger', + }, + { + name: 'High Severity Alerts', + type: 'phrase', + value: 'High', + field: 'data.vulnerability.severity', + }, + { + name: 'Medium Severity Alerts', + type: 'phrase', + value: 'Medium', + field: 'data.vulnerability.severity', + color: 'secondary', + }, + { + name: 'Low Severity Alerts', + type: 'phrase', + value: 'Low', + field: 'data.vulnerability.severity', + color: 'subdued', + }, + ], + virustotal: [ + { + name: 'Total malicious', + type: 'phrase', + value: '1', + field: 'data.virustotal.malicious', + color: 'danger', + }, + { + name: 'Total positives', + type: 'phrase', + value: '0', + negate: true, + field: 'data.virustotal.positives', + color: 'secondary', + }, + { name: 'Total', type: 'total' }, + ], + osquery: [{ name: 'Agents reporting', type: 'unique-count', field: 'agent.id' }], + ciscat: [ + { + name: 'Last scan not checked', + type: 'custom', + filter: { phrase: 'ciscat', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.cis.notchecked' } } }, + }, + }, + color: 'subdued', + }, + { + name: 'Last scan pass', + type: 'custom', + filter: { phrase: 'ciscat', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.cis.pass' } } }, + }, + }, + color: 'secondary', + }, + { + name: 'Last scan score', + type: 'custom', + filter: { phrase: 'ciscat', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.cis.score' } } }, + }, + }, + }, + { + name: 'Last scan date', + type: 'custom', + filter: { phrase: 'ciscat', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.cis.timestamp' } } }, + }, + }, + color: 'secondary', + transformValue: formatUIDate, + }, + { + name: 'Last scan errors', + type: 'custom', + filter: { phrase: 'ciscat', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.cis.error' } } }, + }, + }, + color: 'danger', + }, + { + name: 'Last scan fails', + type: 'custom', + filter: { phrase: 'ciscat', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.cis.fail' } } }, + }, + }, + color: 'danger', + }, + { + name: 'Last scan unknown', + type: 'custom', + filter: { phrase: 'ciscat', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.cis.unknown' } } }, + }, + }, + color: 'subdued', + }, + ], + oscap: [ + { + name: 'Last scan score', + type: 'custom', + filter: { phrase: 'oscap-report', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'timestamp', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.oscap.scan.score' } } }, + }, + }, + }, + { + name: 'Highest scan score', + type: 'custom', + filter: { phrase: 'oscap-report', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'data.oscap.scan.score', order: { _term: 'desc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.oscap.scan.score' } } }, + }, + }, + color: 'secondary', + }, + { + name: 'Lowest scan score', + type: 'custom', + filter: { phrase: 'oscap-report', field: 'rule.groups' }, + agg: { + customAggResult: { + terms: { field: 'data.oscap.scan.score', order: { _term: 'asc' }, size: 1 }, + aggs: { aggResult: { terms: { field: 'data.oscap.scan.score' } } }, + }, + }, + color: 'danger', + }, + ], + }; } - } - async componentDidMount() { - this.indexPattern = await getIndexPattern(); - this.scope = await this.modulesHelper.getDiscoverScope(); - this._isMount = true; - this.buildMetric(); - } + async componentDidMount() { + this.indexPattern = await getIndexPattern(); + this.scope = await this.modulesHelper.getDiscoverScope(); + this._isMount = true; + this.buildMetric(); + } - async getResults(filterParams, aggs = {}){ - const params = {size: 0, track_total_hits: true}; - const result = await getElasticAlerts(this.indexPattern, filterParams, aggs, params ); - let totalHits = 0; - if(Object.keys(aggs).length){ - const agg = (((result.data || {}).aggregations || {})); - if(agg && agg.customAggResult){ //CUSTOM AGG - totalHits = ((((((agg.customAggResult || {}).buckets || [])[0] || {}).aggResult || {}).buckets || [])[0] || {}).key || 0; - }else{ - totalHits = (agg.aggResult || {}).value || 0; + async getResults(filterParams, aggs = {}) { + const params = { size: 0, track_total_hits: true }; + const result = await getElasticAlerts(this.indexPattern, filterParams, aggs, params); + let totalHits = 0; + if (Object.keys(aggs).length) { + const agg = (result.data || {}).aggregations || {}; + if (agg && agg.customAggResult) { + //CUSTOM AGG + totalHits = + ( + (((((agg.customAggResult || {}).buckets || [])[0] || {}).aggResult || {}).buckets || + [])[0] || {} + ).key || 0; + } else { + totalHits = (agg.aggResult || {}).value || 0; + } + } else { + totalHits = (((result.data || {}).hits || {}).total || {}).value || 0; } - }else{ - totalHits = (((result.data || {}).hits || {}).total || {}).value || 0; - } - return totalHits - } + return totalHits; + } - buildMetric(){ - if(!this.metricsList[this.props.section] || !this._isMount) return <>; - const newFilters = this.filterManager.getFilters(); - const searchBarQuery = this.scope.state.query; - const newTime = this.timefilter.getTime(); + async buildMetric() { + if (!this.metricsList[this.props.section] || !this._isMount) return <>; + const newFilters = this.filterManager.getFilters(); + const searchBarQuery = this.scope.state.query; + const newTime = this.timefilter.getTime(); const filterParams = {}; - filterParams["time"] = this.timefilter.getTime(); - filterParams["query"] = searchBarQuery; - filterParams["filters"] = this.filterManager.getFilters(); - this.props.filterAllowedAgents && filterParams["filters"].push(this.props.filterAllowedAgents); - this.setState({filterParams, loading: true}); + filterParams['time'] = this.timefilter.getTime(); + filterParams['query'] = searchBarQuery; + filterParams['filters'] = this.filterManager.getFilters(); + this.props.filterAllowedAgents && + filterParams['filters'].push(this.props.filterAllowedAgents); + this.setState({ filterParams, loading: true }); const newOnClick = {}; - - const result = this.metricsList[this.props.section].map(async(item)=> { + + const result = this.metricsList[this.props.section].map(async (item) => { let filters = []; - if(item.type === 'range'){ + if (item.type === 'range') { const results = {}; const rangeFilterParams = {}; - const valuesArray = {gte: item.gte, lt: item.lt}; + const valuesArray = { gte: item.gte, lt: item.lt }; const filters = { - ...buildRangeFilter({ name: item.field, type: "integer" }, valuesArray, this.indexPattern), - "$state": { "store": "appState" } - } - rangeFilterParams["filters"] = [...filterParams["filters"]] - rangeFilterParams["time"] = filterParams["time"]; - rangeFilterParams["query"] = filterParams["query"]; - rangeFilterParams["filters"].push(filters) - newOnClick[item.name] = () => {this.filterManager.addFilters(filters)}; + ...buildRangeFilter( + { name: item.field, type: 'integer' }, + valuesArray, + this.indexPattern + ), + $state: { store: 'appState' }, + }; + rangeFilterParams['filters'] = [...filterParams['filters']]; + rangeFilterParams['time'] = filterParams['time']; + rangeFilterParams['query'] = filterParams['query']; + rangeFilterParams['filters'].push(filters); + newOnClick[item.name] = () => { + this.filterManager.addFilters(filters); + }; results[item.name] = await this.getResults(rangeFilterParams); - return results ; - }else if(item.type === "phrases"){ + return results; + } else if (item.type === 'phrases') { const results = {}; const phrasesFilter = {}; const filters = { - ...buildPhrasesFilter({ name: item.field, type: "string" }, item.values, this.indexPattern), - "$state": { "store": "appState" } - } - phrasesFilter["filters"] = [...filterParams["filters"]] - phrasesFilter["time"] = filterParams["time"]; - phrasesFilter["query"] = filterParams["query"]; - phrasesFilter["filters"].push(filters); - newOnClick[item.name] = () => {this.filterManager.addFilters(filters)}; + ...buildPhrasesFilter( + { name: item.field, type: 'string' }, + item.values, + this.indexPattern + ), + $state: { store: 'appState' }, + }; + phrasesFilter['filters'] = [...filterParams['filters']]; + phrasesFilter['time'] = filterParams['time']; + phrasesFilter['query'] = filterParams['query']; + phrasesFilter['filters'].push(filters); + newOnClick[item.name] = () => { + this.filterManager.addFilters(filters); + }; results[item.name] = await this.getResults(phrasesFilter); - return results ; - - }else if(item.type === "custom"){ + return results; + } else if (item.type === 'custom') { const results = {}; const customFilters = {}; - - customFilters["filters"] = [...filterParams["filters"]] - customFilters["time"] = filterParams["time"]; - customFilters["query"] = filterParams["query"]; - if(item.filter.phrase){ + + customFilters['filters'] = [...filterParams['filters']]; + customFilters['time'] = filterParams['time']; + customFilters['query'] = filterParams['query']; + if (item.filter.phrase) { const filters = { - ...buildPhraseFilter({ name: item.filter.field, type: "string"}, item.filter.phrase, this.indexPattern), - "$state": { "store": "appState" } - } - customFilters["filters"].push(filters) + ...buildPhraseFilter( + { name: item.filter.field, type: 'string' }, + item.filter.phrase, + this.indexPattern + ), + $state: { store: 'appState' }, + }; + customFilters['filters'].push(filters); } results[item.name] = await this.getResults(customFilters, item.agg); - return results ; - - }else if(item.type === "exists"){ + return results; + } else if (item.type === 'exists') { const results = {}; const existsFilters = {}; const filters = { ...buildExistsFilter({ name: item.field, type: 'nested' }, this.indexPattern), - "$state": { "store": "appState" } - } - existsFilters["filters"] = [...filterParams["filters"]] - existsFilters["time"] = filterParams["time"]; - existsFilters["query"] = filterParams["query"]; - existsFilters["filters"].push(filters); - newOnClick[item.name] = () => {this.filterManager.addFilters(filters)}; + $state: { store: 'appState' }, + }; + existsFilters['filters'] = [...filterParams['filters']]; + existsFilters['time'] = filterParams['time']; + existsFilters['query'] = filterParams['query']; + existsFilters['filters'].push(filters); + newOnClick[item.name] = () => { + this.filterManager.addFilters(filters); + }; results[item.name] = await this.getResults(existsFilters); - return results ; - }else if(item.type === "unique-count"){ + return results; + } else if (item.type === 'unique-count') { const results = {}; const params = {}; const aggs = { - "aggResult" : { - "cardinality" : { - "field" : item.field - } - } - } - - params["filters"] = [...filterParams["filters"]] - params["time"] = filterParams["time"]; - params["query"] = filterParams["query"]; + aggResult: { + cardinality: { + field: item.field, + }, + }, + }; + + params['filters'] = [...filterParams['filters']]; + params['time'] = filterParams['time']; + params['query'] = filterParams['query']; results[item.name] = await this.getResults(params, aggs); - return results ; - }else if(item.type === "phrase"){ + return results; + } else if (item.type === 'phrase') { const results = {}; const phraseFilter = {}; const filters = { - ...buildPhraseFilter({ name: item.field, type: "string"}, item.value, this.indexPattern), - "$state": { "store": "appState" } - } - if(item.negate){ + ...buildPhraseFilter( + { name: item.field, type: 'string' }, + item.value, + this.indexPattern + ), + $state: { store: 'appState' }, + }; + if (item.negate) { filters.meta.negate = item.negate; } - phraseFilter["filters"] = [...filterParams["filters"]] - phraseFilter["time"] = filterParams["time"]; - phraseFilter["query"] = filterParams["query"]; - phraseFilter["filters"].push(filters); - newOnClick[item.name] = () => {this.filterManager.addFilters(filters)}; + phraseFilter['filters'] = [...filterParams['filters']]; + phraseFilter['time'] = filterParams['time']; + phraseFilter['query'] = filterParams['query']; + phraseFilter['filters'].push(filters); + newOnClick[item.name] = () => { + this.filterManager.addFilters(filters); + }; results[item.name] = await this.getResults(phraseFilter); - return results ; - }else{ + return results; + } else { const results = {}; results[item.name] = await this.getResults(filterParams); return results; } }); - - Promise.all(result).then((completed) => { + + try { + const completed = await Promise.all(result); const newResults = {}; - completed.forEach(item => { - const key = Object.keys(item)[0] + completed.forEach((item) => { + const key = Object.keys(item)[0]; newResults[key] = item[key]; }); - this.setState({results: newResults, loading:false, buildingMetrics: false, metricsOnClicks: newOnClick}); - }).catch(error => { - this.setState({loading: false, buildingMetrics: false}); - }); - - } + this.setState({ + results: newResults, + loading: false, + buildingMetrics: false, + metricsOnClicks: newOnClick, + }); + } catch (error) { + this.setState({ loading: false, buildingMetrics: false }); + const options = { + context: `${Metrics.name}.buildMetric`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: error.name || error, + }, + }; + getErrorOrchestrator().handleError(options); + } + } - componentDidUpdate(){ - if(!this.state.buildingMetrics && this.props.resultState === 'ready' && this.state.resultState === 'loading'){ - this.setState({ buildingMetrics: true, resultState: this.props.resultState}, () => { - this.stats = this.buildMetric(); - }); - }else if(this.props.resultState !== this.state.resultState){ - const isLoading = this.props.resultState === 'loading' ? {loading:true} : {}; - this.setState({resultState: this.props.resultState, ...isLoading}); + componentDidUpdate() { + if ( + !this.state.buildingMetrics && + this.props.resultState === 'ready' && + this.state.resultState === 'loading' + ) { + this.setState({ buildingMetrics: true, resultState: this.props.resultState }, () => { + this.stats = this.buildMetric(); + }); + } else if (this.props.resultState !== this.state.resultState) { + const isLoading = this.props.resultState === 'loading' ? { loading: true } : {}; + this.setState({ resultState: this.props.resultState, ...isLoading }); + } } - } - buildTitleButton = (count, itemName) => { - return - 20 ? "2rem" : "2.25rem" }} - onClick={ this.state.metricsOnClicks[itemName] }> - {this.state.results[itemName]} - - - } + buildTitleButton = (count, itemName) => { + return ( + + 20 ? '2rem' : '2.25rem' }} + onClick={this.state.metricsOnClicks[itemName]} + > + {this.state.results[itemName]} + + + ); + }; - buildStatsComp(){ - const { section } = this.props; - if(this.metricsList[section]){ - return this.metricsList[section].map((item,idx) => { - const count = (this.state.results[item.name] || []).length - return( - 20 ? 3 : 1} key={`${item.name}`}> - 20 ? "2rem" : "2.25rem" }}>{item.transformValue ? item.transformValue(this.state.results[item.name]) : this.state.results[item.name]}} - description={item.name} - titleColor={this.metricsList[section][idx].color || 'primary'} - isLoading={this.state.loading} - textAlign="center" - /> - - ) - }); + buildStatsComp() { + const { section } = this.props; + if (this.metricsList[section]) { + return this.metricsList[section].map((item, idx) => { + const count = (this.state.results[item.name] || []).length; + return ( + 20 ? 3 : 1} key={`${item.name}`}> + 20 ? '2rem' : '2.25rem' }}> + {item.transformValue + ? item.transformValue(this.state.results[item.name]) + : this.state.results[item.name]} + + ) + } + description={item.name} + titleColor={this.metricsList[section][idx].color || 'primary'} + isLoading={this.state.loading} + textAlign="center" + /> + + ); + }); + } } - } - render() { - const stats = this.buildStatsComp(); - return ( - + render() { + const stats = this.buildStatsComp(); + return ( + - {stats} + {stats} - ) + ); + } } -}) - +); diff --git a/public/components/overview/mitre/components/tactics/tactics.tsx b/public/components/overview/mitre/components/tactics/tactics.tsx index 4a9df58c9e..9b7c45c8cf 100644 --- a/public/components/overview/mitre/components/tactics/tactics.tsx +++ b/public/components/overview/mitre/components/tactics/tactics.tsx @@ -24,24 +24,27 @@ import { } from '@elastic/eui' import { IFilterParams, getElasticAlerts } from '../../lib'; import { getToasts } from '../../../../../kibana-services'; +import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../../../react-services/common-services'; export class Tactics extends Component { _isMount = false; state: { - tacticsList: Array, - tacticsCount: { key: string, doc_count:number }[], - allSelected : boolean, - loadingAlerts: boolean, - isPopoverOpen: boolean, - firstTime: boolean - } + tacticsList: Array; + tacticsCount: { key: string; doc_count: number }[]; + allSelected: boolean; + loadingAlerts: boolean; + isPopoverOpen: boolean; + firstTime: boolean; + }; props!: { - tacticsObject: object, - selectedTactics: Array - filterParams: IFilterParams - indexPattern: any - onChangeSelectedTactics(selectedTactics): void + tacticsObject: object; + selectedTactics: Array; + filterParams: IFilterParams; + indexPattern: any; + onChangeSelectedTactics(selectedTactics): void; }; constructor(props) { @@ -52,17 +55,17 @@ export class Tactics extends Component { allSelected: false, loadingAlerts: true, isPopoverOpen: false, - firstTime: true - } + firstTime: true, + }; } - async componentDidMount(){ + async componentDidMount() { this._isMount = true; } - initTactics(){ + initTactics() { const tacticsIds = Object.keys(this.props.tacticsObject); - const selectedTactics = {} + const selectedTactics = {}; /*let isMax = {}; tacticsIds.forEach( (item,id) => { if(buckets.length){ @@ -79,36 +82,31 @@ export class Tactics extends Component { selectedTactics[item] = true; } });*/ - tacticsIds.forEach( (item,id) => { + tacticsIds.forEach((item, id) => { selectedTactics[item] = true; }); - this.props.onChangeSelectedTactics(selectedTactics); } - shouldComponentUpdate(nextProps, nextState) { const { filterParams, indexPattern, selectedTactics, isLoading } = this.props; const { tacticsCount, loadingAlerts } = this.state; - if (nextState.loadingAlerts !== loadingAlerts) - return true; - if (nextProps.isLoading !== isLoading) - return true; - if (JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams)) - return true; - if (JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern)) - return true; - if (JSON.stringify(nextState.tacticsCount) !== JSON.stringify(tacticsCount)) - return true; - if (JSON.stringify(nextState.selectedTactics) !== JSON.stringify(selectedTactics)) - return true; + if (nextState.loadingAlerts !== loadingAlerts) return true; + if (nextProps.isLoading !== isLoading) return true; + if (JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams)) return true; + if (JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern)) return true; + if (JSON.stringify(nextState.tacticsCount) !== JSON.stringify(tacticsCount)) return true; + if (JSON.stringify(nextState.selectedTactics) !== JSON.stringify(selectedTactics)) return true; return false; } async componentDidUpdate(prevProps) { const { isLoading, tacticsObject } = this.props; - if (JSON.stringify(prevProps.tacticsObject) !== JSON.stringify(tacticsObject) || isLoading !== prevProps.isLoading){ + if ( + JSON.stringify(prevProps.tacticsObject) !== JSON.stringify(tacticsObject) || + isLoading !== prevProps.isLoading + ) { this.getTacticsCount(this.state.firstTime); } } @@ -118,134 +116,139 @@ export class Tactics extends Component { color: color, title: title, text: text, - toastLifeTimeMs: time + toastLifeTimeMs: time, }); }; async getTacticsCount() { - this.setState({loadingAlerts: true}); + this.setState({ loadingAlerts: true }); const { firstTime } = this.state; - try{ - const {indexPattern, filterParams} = this.props; - if ( !indexPattern ) { return; } + try { + const { indexPattern, filterParams } = this.props; + if (!indexPattern) { + return; + } const aggs = { tactics: { terms: { - field: "rule.mitre.tactic", - size: 1000, - } - } - } - + field: 'rule.mitre.tactic', + size: 1000, + }, + }, + }; + // TODO: use `status` and `statusText` to show errors // @ts-ignore const { data } = await getElasticAlerts(indexPattern, filterParams, aggs); const { buckets } = data.aggregations.tactics; - if(firstTime){ + if (firstTime) { this.initTactics(buckets); // top tactics are checked on component mount } - this._isMount && this.setState({tacticsCount: buckets, loadingAlerts: false, firstTime:false}); - - } catch(err){ - this.showToast( - 'danger', - 'Error', - `Mitre alerts could not be fetched: ${err}`, - 3000 - ); - this.setState({loadingAlerts: false}) + this._isMount && + this.setState({ tacticsCount: buckets, loadingAlerts: false, firstTime: false }); + } catch (error) { + const options = { + context: `${Tactics.name}.getTacticsCount`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Mitre alerts could not be fetched`, + }, + }; + getErrorOrchestrator().handleError(options); + this.setState({ loadingAlerts: false }); } - } - componentWillUnmount() { this._isMount = false; } - facetClicked(id){ + facetClicked(id) { const { selectedTactics: oldSelected, onChangeSelectedTactics } = this.props; const selectedTactics = { ...oldSelected, - [id]: !oldSelected[id] - } + [id]: !oldSelected[id], + }; onChangeSelectedTactics(selectedTactics); } - - getTacticsList(){ + getTacticsList() { const { tacticsCount } = this.state; const { selectedTactics } = this.props; const tacticsIds = Object.keys(this.props.tacticsObject); - const tacticsList:Array = tacticsIds.map( item => { - const quantity = (tacticsCount.find(tactic => tactic.key === item) || {}).doc_count || 0; + const tacticsList: Array = tacticsIds.map((item) => { + const quantity = (tacticsCount.find((tactic) => tactic.key === item) || {}).doc_count || 0; return { id: item, label: item, quantity, onClick: (id) => this.facetClicked(id), - }} - ); - + }; + }); + return ( <> - {tacticsList.sort((a, b) => b.quantity - a.quantity).map(facet => { - let iconNode; - return ( - facet.onClick(facet.id) : undefined - }> - {facet.label} - - ); - })} + {tacticsList + .sort((a, b) => b.quantity - a.quantity) + .map((facet) => { + let iconNode; + return ( + facet.onClick(facet.id) : undefined} + > + {facet.label} + + ); + })} ); - } - checkAllChecked(tacticList: any[]){ + checkAllChecked(tacticList: any[]) { const { selectedTactics } = this.props; let allSelected = true; - tacticList.forEach( item => { - if(!selectedTactics[item.id]) - allSelected = false; + tacticList.forEach((item) => { + if (!selectedTactics[item.id]) allSelected = false; }); - if(allSelected !== this.state.allSelected){ - this.setState({allSelected}); + if (allSelected !== this.state.allSelected) { + this.setState({ allSelected }); } } - onCheckAllClick(){ - const allSelected = ! this.state.allSelected; - const {selectedTactics, onChangeSelectedTactics} = this.props; - Object.keys(selectedTactics).map( item => { + onCheckAllClick() { + const allSelected = !this.state.allSelected; + const { selectedTactics, onChangeSelectedTactics } = this.props; + Object.keys(selectedTactics).map((item) => { selectedTactics[item] = allSelected; }); - - this.setState({allSelected}); + + this.setState({ allSelected }); onChangeSelectedTactics(selectedTactics); } - - onGearButtonClick(){ - this.setState({isPopoverOpen: !this.state.isPopoverOpen}); + + onGearButtonClick() { + this.setState({ isPopoverOpen: !this.state.isPopoverOpen }); } - - closePopover(){ - this.setState({isPopoverOpen: false}); + + closePopover() { + this.setState({ isPopoverOpen: false }); } - selectAll(status){ - const {selectedTactics, onChangeSelectedTactics} = this.props; - Object.keys(selectedTactics).map( item => { + selectAll(status) { + const { selectedTactics, onChangeSelectedTactics } = this.props; + Object.keys(selectedTactics).map((item) => { selectedTactics[item] = status; }); onChangeSelectedTactics(selectedTactics); @@ -273,11 +276,11 @@ export class Tactics extends Component { this.selectAll(false); }, }, - ] - } - ] + ], + }, + ]; return ( -
+
@@ -285,23 +288,31 @@ export class Tactics extends Component { - + this.onGearButtonClick()} aria-label={'tactics options'}>)} + button={ + this.onGearButtonClick()} + aria-label={'tactics options'} + > + } isOpen={this.state.isPopoverOpen} panelPaddingSize="none" - closePopover={() => this.closePopover()}> - + closePopover={() => this.closePopover()} + > + - { this.props.isLoading - ? - : - {this.getTacticsList()} - - } + {this.props.isLoading ? ( + + + + ) : ( + {this.getTacticsList()} + )}
- ) + ); } } diff --git a/public/components/overview/mitre/components/techniques/components/flyout-technique/flyout-technique.tsx b/public/components/overview/mitre/components/techniques/components/flyout-technique/flyout-technique.tsx index 99dda5b392..6fa1b41b1b 100644 --- a/public/components/overview/mitre/components/techniques/components/flyout-technique/flyout-technique.tsx +++ b/public/components/overview/mitre/components/techniques/components/flyout-technique/flyout-technique.tsx @@ -41,6 +41,9 @@ import { AppNavigate } from '../../../../../../../react-services/app-navigate'; import { Discover } from '../../../../../../common/modules/discover'; import { getUiSettings } from '../../../../../../../kibana-services'; import { FilterManager } from '../../../../../../../../../../src/plugins/data/public/'; +import { UI_LOGGER_LEVELS } from '../../../../../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../../../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../../../../../react-services/common-services'; export class FlyoutTechnique extends Component { _isMount = false; @@ -126,7 +129,20 @@ export class FlyoutTechnique extends Component { }); const rawData = (((result || {}).data || {}).data || {}).affected_items !!rawData && this.formatTechniqueData(rawData[0]); - }catch(err){ + }catch(error){ + const options = { + context: `${FlyoutTechnique.name}.getTechniqueData`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Error obtaining the requested technique`, + }, + }; + getErrorOrchestrator().handleError(options); this.setState({loading: false}); } } diff --git a/public/components/overview/mitre/components/techniques/techniques.tsx b/public/components/overview/mitre/components/techniques/techniques.tsx index 8d32c76646..b7e0964c0d 100644 --- a/public/components/overview/mitre/components/techniques/techniques.tsx +++ b/public/components/overview/mitre/components/techniques/techniques.tsx @@ -9,7 +9,7 @@ * * Find more information about this on the LICENSE file. */ -import React, { Component, Fragment } from 'react' +import React, { Component, Fragment } from 'react'; import { EuiFacetButton, EuiFlexGroup, @@ -33,446 +33,542 @@ import { getElasticAlerts, IFilterParams } from '../../lib'; import { ITactic } from '../../'; import { withWindowSize } from '../../../../../components/common/hocs/withWindowSize'; import { WzRequest } from '../../../../../react-services/wz-request'; -import {WAZUH_ALERTS_PATTERN} from '../../../../../../common/constants'; +import { WAZUH_ALERTS_PATTERN } from '../../../../../../common/constants'; import { AppState } from '../../../../../react-services/app-state'; import { WzFieldSearchDelay } from '../../../../common/search'; import { getDataPlugin, getToasts } from '../../../../../kibana-services'; +import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../../../react-services/common-services'; + +export const Techniques = withWindowSize( + class Techniques extends Component { + _isMount = false; + + props!: { + tacticsObject: ITactic; + selectedTactics: any; + indexPattern: any; + filterParams: IFilterParams; + }; + + state: { + techniquesCount: { key: string; doc_count: number }[]; + isFlyoutVisible: Boolean; + currentTechniqueData: {}; + currentTechnique: string; + hideAlerts: boolean; + actionsOpen: string; + filteredTechniques: boolean | [string]; + mitreTechniques: []; + isSearching: boolean; + }; + + constructor(props) { + super(props); + + this.state = { + isFlyoutVisible: false, + currentTechniqueData: {}, + techniquesCount: [], + currentTechnique: '', + hideAlerts: false, + actionsOpen: '', + filteredTechniques: false, + mitreTechniques: [], + isSearching: false, + }; + this.onChangeFlyout.bind(this); + } -export const Techniques = withWindowSize(class Techniques extends Component { - _isMount = false; - - props!: { - tacticsObject: ITactic - selectedTactics: any - indexPattern: any - filterParams: IFilterParams - } - - state: { - techniquesCount: {key: string, doc_count: number}[] - isFlyoutVisible: Boolean, - currentTechniqueData: {}, - currentTechnique: string, - hideAlerts: boolean, - actionsOpen: string, - filteredTechniques: boolean | [string] - mitreTechniques: [], - isSearching: boolean - } + async componentDidMount() { + this._isMount = true; + await this.buildMitreTechniquesFromApi(); + } - constructor(props) { - super(props); - - this.state = { - isFlyoutVisible: false, - currentTechniqueData: {}, - techniquesCount: [], - currentTechnique: '', - hideAlerts: false, - actionsOpen: "", - filteredTechniques: false, - mitreTechniques: [], - isSearching: false + shouldComponentUpdate(nextProps, nextState) { + const { filterParams, indexPattern, selectedTactics, isLoading } = this.props; + if (nextProps.isLoading !== isLoading) return true; + if (JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams)) return true; + if (JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern)) return true; + if (JSON.stringify(nextState.selectedTactics) !== JSON.stringify(selectedTactics)) + return true; + return false; } - this.onChangeFlyout.bind(this); - } - - async componentDidMount(){ - this._isMount = true; - await this.buildMitreTechniquesFromApi(); - } - shouldComponentUpdate(nextProps, nextState) { - const { filterParams, indexPattern, selectedTactics, isLoading } = this.props; - if (nextProps.isLoading !== isLoading) - return true; - if (JSON.stringify(nextProps.filterParams) !== JSON.stringify(filterParams)) - return true; - if (JSON.stringify(nextProps.indexPattern) !== JSON.stringify(indexPattern)) - return true; - if (JSON.stringify(nextState.selectedTactics) !== JSON.stringify(selectedTactics)) - return true; - return false; - } + componentDidUpdate(prevProps) { + const { isLoading, tacticsObject, filters } = this.props; + if ( + JSON.stringify(prevProps.tacticsObject) !== JSON.stringify(tacticsObject) || + isLoading !== prevProps.isLoading + ) + this.getTechniquesCount(); + } - componentDidUpdate(prevProps) { - const { isLoading, tacticsObject, filters } = this.props; - if ( JSON.stringify(prevProps.tacticsObject) !== JSON.stringify(tacticsObject) || (isLoading !== prevProps.isLoading)) - this.getTechniquesCount(); - } + componentWillUnmount() { + this._isMount = false; + } - componentWillUnmount() { - this._isMount = false; - } + showToast(color: string, title: string = '', text: string = '', time: number = 3000) { + getToasts().add({ + color: color, + title: title, + text: text, + toastLifeTimeMs: time, + }); + } - showToast(color: string, title: string = '', text: string = '', time: number = 3000) { - getToasts().add({ - color: color, - title: title, - text: text, - toastLifeTimeMs: time, - }); - }; - - async getTechniquesCount() { - try{ - const {indexPattern, filters} = this.props; - if ( !indexPattern ) { return; } - const aggs = { - techniques: { - terms: { - field: "rule.mitre.id", - size: 1000, - } + async getTechniquesCount() { + try { + const { indexPattern, filters } = this.props; + if (!indexPattern) { + return; } + const aggs = { + techniques: { + terms: { + field: 'rule.mitre.id', + size: 1000, + }, + }, + }; + this._isMount && this.setState({ loadingAlerts: true }); + // TODO: use `status` and `statusText` to show errors + // @ts-ignore + const { data, status, statusText } = await getElasticAlerts(indexPattern, filters, aggs); + const { buckets } = data.aggregations.techniques; + this._isMount && this.setState({ techniquesCount: buckets, loadingAlerts: false }); + } catch (error) { + const options = { + context: `${Techniques.name}.getTechniquesCount`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Mitre alerts could not be fetched`, + }, + }; + getErrorOrchestrator().handleError(options); + this._isMount && this.setState({ loadingAlerts: false }); } - this._isMount && this.setState({loadingAlerts: true}); - // TODO: use `status` and `statusText` to show errors - // @ts-ignore - const {data, status, statusText, } = await getElasticAlerts(indexPattern, filters, aggs); - const { buckets } = data.aggregations.techniques; - this._isMount && this.setState({techniquesCount: buckets, loadingAlerts: false}); - - } catch(err){ - // this.showToast( - // 'danger', - // 'Error', - // `Mitre alerts could not be fetched: ${err}`, - // 3000 - // ); - this._isMount && this.setState({loadingAlerts: false}) } - } - buildPanel(techniqueID){ - return [ - { - id: 0, - title: 'Actions', - items: [ - { - name: 'Filter for value', - icon: , - onClick: () => { - this.closeActionsMenu(); - this.addFilter({key: 'rule.mitre.id', value: techniqueID, negate: false} ); + buildPanel(techniqueID) { + return [ + { + id: 0, + title: 'Actions', + items: [ + { + name: 'Filter for value', + icon: , + onClick: () => { + this.closeActionsMenu(); + this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: false }); + }, }, - }, - { - name: 'Filter out value', - icon: , - onClick: () => { - this.closeActionsMenu(); - this.addFilter({key: 'rule.mitre.id', value: techniqueID, negate: true} ); + { + name: 'Filter out value', + icon: , + onClick: () => { + this.closeActionsMenu(); + this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: true }); + }, }, - }, - { - name: 'View technique details', - icon: , - onClick: () => { - this.closeActionsMenu(); - this.showFlyout(techniqueID) + { + name: 'View technique details', + icon: , + onClick: () => { + this.closeActionsMenu(); + this.showFlyout(techniqueID); + }, }, - } - ], - } - ] - } - - techniqueColumnsResponsive(){ - if(this.props && this.props.windowSize){ - return this.props.windowSize.width < 930 ? 2 - : this.props.windowSize.width < 1200 ? 3 - : 4; - }else{ - return 4; + ], + }, + ]; } - } - async getMitreTechniques (params) { - try{ - return await WzRequest.apiReq("GET", "/mitre/techniques", { params }); - }catch(error){ - this.showToast( - 'danger', - 'Error', - `Mitre techniques could not be fetched: ${error}`, - 3000 - ); - return []; + techniqueColumnsResponsive() { + if (this.props && this.props.windowSize) { + return this.props.windowSize.width < 930 ? 2 : this.props.windowSize.width < 1200 ? 3 : 4; + } else { + return 4; + } } - } - async buildMitreTechniquesFromApi () { - const limitResults = 500; - const params = { limit: limitResults }; - this.setState({ isSearching: true }); - const output = await this.getMitreTechniques(params); - const totalItems = (((output || {}).data || {}).data || {}).total_affected_items; - let mitreTechniques = []; - mitreTechniques.push(...output.data.data.affected_items); - if (totalItems && output.data && output.data.data && totalItems > limitResults) { - const extraResults = await Promise.all( - Array(Math.ceil((totalItems-params.limit)/params.limit)).fill() - .map(async (_,index) => { - const response = await this.getMitreTechniques({...params, offset: limitResults * (1+index)}); - return response.data.data.affected_items; - }) - ); - mitreTechniques.push(...extraResults.flat()); + async getMitreTechniques(params) { + try { + return await WzRequest.apiReq('GET', '/mitre/techniques', { params }); + } catch (error) { + const options = { + context: `${Techniques.name}.getMitreTechniques`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Mitre techniques could not be fetched`, + }, + }; + getErrorOrchestrator().handleError(options); + return []; + } } - this.setState({ mitreTechniques: mitreTechniques, isSearching: false }); - } - buildObjTechniques(techniques){ - const techniquesObj = []; - techniques.forEach(element => { - const mitreObj = this.state.mitreTechniques.find(item => item.id === element); - if(mitreObj){ - const mitreTechniqueName = mitreObj.name; - const mitreTechniqueID = mitreObj.references.find(item => item.source === "mitre-attack").external_id; - mitreTechniqueID ? techniquesObj.push({ id : mitreTechniqueID, name: mitreTechniqueName}) : ''; + async buildMitreTechniquesFromApi() { + const limitResults = 500; + const params = { limit: limitResults }; + this.setState({ isSearching: true }); + const output = await this.getMitreTechniques(params); + const totalItems = (((output || {}).data || {}).data || {}).total_affected_items; + let mitreTechniques = []; + mitreTechniques.push(...output.data.data.affected_items); + if (totalItems && output.data && output.data.data && totalItems > limitResults) { + const extraResults = await Promise.all( + Array(Math.ceil((totalItems - params.limit) / params.limit)) + .fill() + .map(async (_, index) => { + const response = await this.getMitreTechniques({ + ...params, + offset: limitResults * (1 + index), + }); + return response.data.data.affected_items; + }) + ); + mitreTechniques.push(...extraResults.flat()); } - }); - return techniquesObj; - } + this.setState({ mitreTechniques: mitreTechniques, isSearching: false }); + } - renderFacet() { - const { tacticsObject } = this.props; - const { techniquesCount } = this.state; - let hash = {}; - let tacticsToRender: Array = []; - const currentTechniques = Object.keys(tacticsObject).map(tacticsKey => ({tactic: tacticsKey, techniques: this.buildObjTechniques(tacticsObject[tacticsKey].techniques)})) - .filter(tactic => this.props.selectedTactics[tactic.tactic]) - .map(tactic => tactic.techniques) - .flat() - .filter((techniqueID, index, array) => array.indexOf(techniqueID) === index); - tacticsToRender = currentTechniques - .filter(technique => this.state.filteredTechniques ? this.state.filteredTechniques.includes(technique.id) : technique.id && hash[technique.id] ? false : hash[technique.id] = true) - .map(technique => { - return { - id: technique.id, - label: `${technique.id} - ${technique.name}`, - quantity: (techniquesCount.find(item => item.key === technique.id) || {}).doc_count || 0 + buildObjTechniques(techniques) { + const techniquesObj = []; + techniques.forEach((element) => { + const mitreObj = this.state.mitreTechniques.find((item) => item.id === element); + if (mitreObj) { + const mitreTechniqueName = mitreObj.name; + const mitreTechniqueID = mitreObj.references.find( + (item) => item.source === 'mitre-attack' + ).external_id; + mitreTechniqueID + ? techniquesObj.push({ id: mitreTechniqueID, name: mitreTechniqueName }) + : ''; } - }) - .filter(technique => this.state.hideAlerts ? technique.quantity !== 0 : true); - const tacticsToRenderOrdered = tacticsToRender.sort((a, b) => b.quantity - a.quantity).map( (item,idx) => { - const tooltipContent = `View details of ${item.label} (${item.id})`; - const toolTipAnchorClass = "wz-display-inline-grid" + (this.state.hover=== item.id ? " wz-mitre-width" : " "); - return( - this.setState({ hover: item.id })} - onMouseLeave={() => this.setState({ hover: "" })} - key={idx} style={{border: "1px solid #8080804a", maxWidth: 'calc(25% - 8px)', maxHeight: 41}}> - - this.showFlyout(item.id)}> - - - {item.label} - - - - {this.state.hover === item.id && - - - {this.openDashboard(e,item.id);e.stopPropagation()}} color="primary" type="visualizeApp"> -   - - {this.openDiscover(e,item.id);e.stopPropagation()}} color="primary" type="discoverApp"> - - - - } - - ) - } - isOpen={this.state.actionsOpen === item.id} - closePopover={() => this.closeActionsMenu()} - panelPaddingSize="none" - style={{width: "100%"}} - anchorPosition="downLeft"> - - - - ); - - }) - if(this.state.isSearching || this.state.loadingAlerts || this.props.isLoading){ - return ( - - - - ) + }); + return techniquesObj; } - if(tacticsToRender.length){ - return ( - - {tacticsToRenderOrdered} - - ) - }else{ - return - } - } - - openDiscover(e,techniqueID){ - this.addFilter({key: 'rule.mitre.id', value: techniqueID, negate: false} ); - this.props.onSelectedTabChanged('events'); - } + renderFacet() { + const { tacticsObject } = this.props; + const { techniquesCount } = this.state; + let hash = {}; + let tacticsToRender: Array = []; + const currentTechniques = Object.keys(tacticsObject) + .map((tacticsKey) => ({ + tactic: tacticsKey, + techniques: this.buildObjTechniques(tacticsObject[tacticsKey].techniques), + })) + .filter((tactic) => this.props.selectedTactics[tactic.tactic]) + .map((tactic) => tactic.techniques) + .flat() + .filter((techniqueID, index, array) => array.indexOf(techniqueID) === index); + tacticsToRender = currentTechniques + .filter((technique) => + this.state.filteredTechniques + ? this.state.filteredTechniques.includes(technique.id) + : technique.id && hash[technique.id] + ? false + : (hash[technique.id] = true) + ) + .map((technique) => { + return { + id: technique.id, + label: `${technique.id} - ${technique.name}`, + quantity: + (techniquesCount.find((item) => item.key === technique.id) || {}).doc_count || 0, + }; + }) + .filter((technique) => (this.state.hideAlerts ? technique.quantity !== 0 : true)); + const tacticsToRenderOrdered = tacticsToRender + .sort((a, b) => b.quantity - a.quantity) + .map((item, idx) => { + const tooltipContent = `View details of ${item.label} (${item.id})`; + const toolTipAnchorClass = + 'wz-display-inline-grid' + (this.state.hover === item.id ? ' wz-mitre-width' : ' '); + return ( + this.setState({ hover: item.id })} + onMouseLeave={() => this.setState({ hover: '' })} + key={idx} + style={{ border: '1px solid #8080804a', maxWidth: 'calc(25% - 8px)', maxHeight: 41 }} + > + this.showFlyout(item.id)} + > + + + {item.label} + + + + {this.state.hover === item.id && ( + + + { + this.openDashboard(e, item.id); + e.stopPropagation(); + }} + color="primary" + type="visualizeApp" + > + {' '} +   + + { + this.openDiscover(e, item.id); + e.stopPropagation(); + }} + color="primary" + type="discoverApp" + > + + + )} + + } + isOpen={this.state.actionsOpen === item.id} + closePopover={() => this.closeActionsMenu()} + panelPaddingSize="none" + style={{ width: '100%' }} + anchorPosition="downLeft" + > + + + + ); + }); + if (this.state.isSearching || this.state.loadingAlerts || this.props.isLoading) { + return ( + + + + ); + } + if (tacticsToRender.length) { + return ( + + {tacticsToRenderOrdered} + + ); + } else { + return ( + + ); + } + } - openDashboard(e,techniqueID){ - this.addFilter({key: 'rule.mitre.id', value: techniqueID, negate: false} ); - this.props.onSelectedTabChanged('dashboard'); - } + openDiscover(e, techniqueID) { + this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: false }); + this.props.onSelectedTabChanged('events'); + } - openIntelligence(e,redirectTo,itemId){ - this.props.onSelectedTabChanged('intelligence'); - window.location.href = window.location+`&tabRedirect=${redirectTo}&idToRedirect=${itemId}` - } + openDashboard(e, techniqueID) { + this.addFilter({ key: 'rule.mitre.id', value: techniqueID, negate: false }); + this.props.onSelectedTabChanged('dashboard'); + } - /** - * Adds a new filter with format { "filter_key" : "filter_value" }, e.g. {"agent.id": "001"} - * @param filter - */ - addFilter(filter) { - const { filterManager } = getDataPlugin().query; - const matchPhrase = {}; - matchPhrase[filter.key] = filter.value; - const newFilter = { - "meta": { - "disabled": false, - "key": filter.key, - "params": { "query": filter.value }, - "type": "phrase", - "negate": filter.negate || false, - "index": AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN - }, - "query": { "match_phrase": matchPhrase }, - "$state": { "store": "appState" } + openIntelligence(e, redirectTo, itemId) { + this.props.onSelectedTabChanged('intelligence'); + window.location.href = window.location + `&tabRedirect=${redirectTo}&idToRedirect=${itemId}`; } - filterManager.addFilters([newFilter]); - } - onChange = searchValue => { - if(!searchValue){ - this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); + /** + * Adds a new filter with format { "filter_key" : "filter_value" }, e.g. {"agent.id": "001"} + * @param filter + */ + addFilter(filter) { + const { filterManager } = getDataPlugin().query; + const matchPhrase = {}; + matchPhrase[filter.key] = filter.value; + const newFilter = { + meta: { + disabled: false, + key: filter.key, + params: { query: filter.value }, + type: 'phrase', + negate: filter.negate || false, + index: AppState.getCurrentPattern() || WAZUH_ALERTS_PATTERN, + }, + query: { match_phrase: matchPhrase }, + $state: { store: 'appState' }, + }; + filterManager.addFilters([newFilter]); } - } - onSearch = async searchValue => { - try{ - if(searchValue){ - this._isMount && this.setState({isSearching: true}); - const response = await WzRequest.apiReq('GET', '/mitre/techniques', { - params: { - search: searchValue, - limit: 500 - } - }); - const filteredTechniques = ((((response || {}).data || {}).data).affected_items || []).map(item => item.references.filter(reference => reference.source === "mitre-attack")[0].external_id); - this._isMount && this.setState({ filteredTechniques, isSearching: false }); - }else{ + onChange = (searchValue) => { + if (!searchValue) { this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); } - }catch(error){ - this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); + }; + + onSearch = async (searchValue) => { + try { + if (searchValue) { + this._isMount && this.setState({ isSearching: true }); + const response = await WzRequest.apiReq('GET', '/mitre/techniques', { + params: { + search: searchValue, + limit: 500, + }, + }); + const filteredTechniques = (((response || {}).data || {}).data.affected_items || []).map( + (item) => + item.references.filter((reference) => reference.source === 'mitre-attack')[0] + .external_id + ); + this._isMount && this.setState({ filteredTechniques, isSearching: false }); + } else { + this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); + } + } catch (error) { + const options = { + context: `${Techniques.name}.onSearch`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: error.name || error, + }, + }; + getErrorOrchestrator().handleError(options); + this._isMount && this.setState({ filteredTechniques: false, isSearching: false }); + } + }; + async closeActionsMenu() { + this.setState({ actionsOpen: false }); } - } - async closeActionsMenu() { - this.setState({actionsOpen: false}); - } - async showActionsMenu(techniqueData) { - this.setState({actionsOpen: techniqueData }); - } + async showActionsMenu(techniqueData) { + this.setState({ actionsOpen: techniqueData }); + } - async showFlyout(techniqueData) { - this.setState({isFlyoutVisible: true, currentTechnique: techniqueData }); - } + async showFlyout(techniqueData) { + this.setState({ isFlyoutVisible: true, currentTechnique: techniqueData }); + } - closeFlyout() { - this.setState({ isFlyoutVisible: false, currentTechniqueData: {}, }); - } + closeFlyout() { + this.setState({ isFlyoutVisible: false, currentTechniqueData: {} }); + } - onChangeFlyout = (isFlyoutVisible: boolean) => { + onChangeFlyout = (isFlyoutVisible: boolean) => { this.setState({ isFlyoutVisible }); - } + }; - hideAlerts(){ - this.setState({hideAlerts: !this.state.hideAlerts}) - } - - render() { - const { isFlyoutVisible, currentTechnique } = this.state; - return ( -
- - - -

Techniques

-
-
+ hideAlerts() { + this.setState({ hideAlerts: !this.state.hideAlerts }); + } - - - - + render() { + const { isFlyoutVisible, currentTechnique } = this.state; + return ( +
+ + + +

Techniques

+
+
+ + + + + Hide techniques with no alerts   - this.hideAlerts()} - /> + this.hideAlerts()} + /> - - - -
- - - - - -
- {this.renderFacet()} + + + + + + + + + +
{this.renderFacet()}
+ {isFlyoutVisible && ( + this.onChangeFlyout(false)} + > + this.openDashboard(e, itemId)} + openDiscover={(e, itemId) => this.openDiscover(e, itemId)} + openIntelligence={(e, redirectTo, itemId) => + this.openIntelligence(e, redirectTo, itemId) + } + onChangeFlyout={this.onChangeFlyout} + currentTechniqueData={this.state.currentTechniqueData} + currentTechnique={currentTechnique} + tacticsObject={this.props.tacticsObject} + /> + + )}
- { isFlyoutVisible && - this.onChangeFlyout(false) } > - this.openDashboard(e,itemId)} - openDiscover={(e,itemId) => this.openDiscover(e,itemId)} - openIntelligence={(e,redirectTo,itemId) => this.openIntelligence(e,redirectTo,itemId)} - onChangeFlyout={this.onChangeFlyout} - currentTechniqueData={this.state.currentTechniqueData} - currentTechnique={currentTechnique} - tacticsObject={this.props.tacticsObject} /> - - } -
- ) - } -}) + ); + } + } +); diff --git a/public/components/overview/mitre/mitre.tsx b/public/components/overview/mitre/mitre.tsx index 3264a55e4d..83eed4531a 100644 --- a/public/components/overview/mitre/mitre.tsx +++ b/public/components/overview/mitre/mitre.tsx @@ -25,7 +25,10 @@ import { KbnSearchBar } from '../../kbn-search-bar'; import { TimeRange, Query } from '../../../../../../src/plugins/data/common'; import { ModulesHelper } from '../../common/modules/modules-helper'; import { getDataPlugin, getToasts } from '../../../kibana-services'; -import { withErrorBoundary } from "../../common/hocs" +import { withErrorBoundary } from "../../common/hocs"; +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; export interface ITactic { [key:string]: string[] @@ -122,14 +125,21 @@ export const Mitre = withErrorBoundary (class Mitre extends Component { tacticsObject[item.name] = item; }); this._isMount && this.setState({tacticsObject, isLoading: false}); - } catch(err) { + } catch(error) { this.setState({ isLoading: false }); - this.showToast( - 'danger', - 'Error', - `Mitre data could not be fetched: ${err}`, - 3000 - ); + const options = { + context: `${Mitre.name}.buildTacticsObject`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Mitre data could not be fetched`, + }, + }; + getErrorOrchestrator().handleError(options); } } From 9ea3f5324341bb13b4b88a3f2c38d5c7b9701787 Mon Sep 17 00:00:00 2001 From: pablomarga Date: Tue, 6 Jul 2021 16:59:42 +0200 Subject: [PATCH 2/3] Resources --- .../mitre_attack_intelligence/resource.tsx | 19 +++++++++++++++++-- .../resource_detail_references_table.tsx | 19 ++++++++++++++++++- .../mitre_attack_intelligence/resources.tsx | 16 ++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/public/components/overview/mitre_attack_intelligence/resource.tsx b/public/components/overview/mitre_attack_intelligence/resource.tsx index 8782b4be54..85ebe6deab 100644 --- a/public/components/overview/mitre_attack_intelligence/resource.tsx +++ b/public/components/overview/mitre_attack_intelligence/resource.tsx @@ -15,6 +15,9 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { TableWzAPI } from '../../../components/common/tables'; import { WzRequest } from '../../../react-services'; import { ModuleMitreAttackIntelligenceFlyout } from './resource_detail_flyout'; +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; export const ModuleMitreAttackIntelligenceResource = ({ label, @@ -50,8 +53,20 @@ export const ModuleMitreAttackIntelligenceResource = ({ )?.external_id, })); setDetails(data[0]); - } catch { - return {}; + } catch (error) { + const options = { + context: `${ModuleMitreAttackIntelligenceResource.name}.getMitreItemToRedirect`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: error.name || error, + }, + }; + getErrorOrchestrator().handleError(options); } }; diff --git a/public/components/overview/mitre_attack_intelligence/resource_detail_references_table.tsx b/public/components/overview/mitre_attack_intelligence/resource_detail_references_table.tsx index 6861e48a89..3a89f0b326 100644 --- a/public/components/overview/mitre_attack_intelligence/resource_detail_references_table.tsx +++ b/public/components/overview/mitre_attack_intelligence/resource_detail_references_table.tsx @@ -18,6 +18,9 @@ import { SortDirection, EuiInMemoryTable, } from '@elastic/eui'; +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; type backToTopType = () => void; @@ -55,7 +58,21 @@ export const ReferencesTable = ({referencesName, referencesArray, columns, backT })); setData(data.flat()); } - catch (error){}; + catch (error){ + const options = { + context: `${ReferencesTable.name}.getValues`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: error.name || error, + }, + }; + getErrorOrchestrator().handleError(options); + }; setIsLoading(false); }; diff --git a/public/components/overview/mitre_attack_intelligence/resources.tsx b/public/components/overview/mitre_attack_intelligence/resources.tsx index 320e4d3e9b..e3d9d25d30 100644 --- a/public/components/overview/mitre_attack_intelligence/resources.tsx +++ b/public/components/overview/mitre_attack_intelligence/resources.tsx @@ -16,6 +16,9 @@ import { Markdown } from '../../common/util'; import { formatUIDate } from '../../../react-services'; import React from 'react'; import { EuiLink } from '@elastic/eui'; +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; const getMitreAttackIntelligenceSuggestions = (endpoint: string, field: string) => async (input: string) => { try{ @@ -30,6 +33,19 @@ const getMitreAttackIntelligenceSuggestions = (endpoint: string, field: string) .sort() .slice(0,9) }catch(error){ + const options = { + context: `${ModuleMitreAttackIntelligenceResource.name}.getMitreItemToRedirect`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + display: true, + error: { + error: error, + message: error.message || error, + title: `Error getting suggestions`, + }, + }; + getErrorOrchestrator().handleError(options); return []; }; }; From 32895f8dbfd6cdaad737a5414ffbcf190ddf5294 Mon Sep 17 00:00:00 2001 From: pablomarga Date: Thu, 8 Jul 2021 10:16:51 +0200 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 706d005bc9..c4bb2d5355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Added try catch strategy with ErrorOrchestrator service on Overview [#3408](https://github.com/wazuh/wazuh-kibana-app/pull/3408) - Added try catch strategy with ErrorOrchestrator service on ManagementController [#3374](https://github.com/wazuh/wazuh-kibana-app/pull/3374) - Added try catch strategy with ErrorOrchestrator service on FIM & SCA sections [#3417](https://github.com/wazuh/wazuh-kibana-app/pull/3417) +- Added try catch strategy with ErrorOrchestrator service on Components > Overview [#3442](https://github.com/wazuh/wazuh-kibana-app/pull/3442) ### Changed