From 72289d5e18e85790b806f6e143f476e0301ca26a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Chantal=20Bel=C3=A9n=20kelm?=
<99441266+chantal-kelm@users.noreply.github.com>
Date: Thu, 21 Dec 2023 06:37:14 -0300
Subject: [PATCH] Develop logic of a new index for the fim module (#6227)
* Create index pattern to be used in file integrity monitoring module and create checks on the health-check of the index pattern
* adjustments to the health check for the fim index pattern
* tab dashboard name change
* merge with master
* delete console.log
* update changelog
* correction of unit test of fim
* correction of unit test of fim
* correction of unit test of fim
* correction of unit test of fim
---
CHANGELOG.md | 1 +
plugins/main/common/config-equivalences.js | 2 +
plugins/main/common/constants.ts | 30 +
.../common/modules/modules-defaults.js | 17 +-
.../health-check.container.test.tsx.snap | 17 +
.../container/health-check.container.test.tsx | 1 +
.../container/health-check.container.tsx | 14 +
.../overview/fim/dashboard/dashboard.tsx | 108 +++
.../fim/dashboard/dashboard_panels.ts | 682 ++++++++++++++++++
.../fim/dashboard/dashboard_panels_filters.ts | 160 ++++
.../fim/dashboard/dashboard_panels_kpis.ts | 416 +++++++++++
.../overview/fim/dashboard/fim_filters.scss | 6 +
.../overview/fim/dashboard/index.tsx | 1 +
.../overview/fim/inventory/config/index.ts | 30 +
.../overview/fim/inventory/index.tsx | 1 +
.../overview/fim/inventory/inventory.scss | 13 +
.../overview/fim/inventory/inventory.tsx | 244 +++++++
.../fim/inventory/inventory_service.ts | 224 ++++++
.../redux/reducers/appConfigReducers.ts | 31 +-
.../fixtures/configuration.panel.text.json | 5 +
plugins/wazuh-core/common/constants.ts | 27 +
21 files changed, 2014 insertions(+), 16 deletions(-)
create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard.tsx
create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts
create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts
create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts
create mode 100644 plugins/main/public/components/overview/fim/dashboard/fim_filters.scss
create mode 100644 plugins/main/public/components/overview/fim/dashboard/index.tsx
create mode 100644 plugins/main/public/components/overview/fim/inventory/config/index.ts
create mode 100644 plugins/main/public/components/overview/fim/inventory/index.tsx
create mode 100644 plugins/main/public/components/overview/fim/inventory/inventory.scss
create mode 100644 plugins/main/public/components/overview/fim/inventory/inventory.tsx
create mode 100644 plugins/main/public/components/overview/fim/inventory/inventory_service.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02971f0fce..7f44ba8f6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Support for Wazuh 4.9.0
- Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145)
- Remove embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120)
+- Develop logic of a new index for the fim module [#6227](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6227)
## Wazuh v4.8.1 - OpenSearch Dashboards 2.10.0 - Revision 00
diff --git a/plugins/main/common/config-equivalences.js b/plugins/main/common/config-equivalences.js
index fcfd359454..83aebea711 100644
--- a/plugins/main/common/config-equivalences.js
+++ b/plugins/main/common/config-equivalences.js
@@ -98,6 +98,8 @@ export const nameEquivalence = {
'alerts.sample.prefix': 'Sample alerts prefix',
'vulnerabilities.pattern': 'Index pattern',
'checks.vulnerabilities.pattern': 'Vulnerabilities index pattern',
+ 'fim.pattern': 'Index pattern',
+ 'checks.fim.pattern': 'Fim index pattern',
};
const HEALTH_CHECK = 'Health Check';
diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts
index 4949a30685..46831badb9 100644
--- a/plugins/main/common/constants.ts
+++ b/plugins/main/common/constants.ts
@@ -51,6 +51,9 @@ export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *';
// Wazuh vulnerabilities
export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities';
+// Wazuh fim
+export const WAZUH_FIM_PATTERN = 'wazuh-states-fim';
+
// Job - Wazuh initialize
export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana';
@@ -861,6 +864,33 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = {
return schema.boolean();
},
},
+ 'checks.fim.pattern': {
+ title: 'Fim index pattern',
+ description:
+ 'Enable or disable the fim index pattern health check when opening the app.',
+ category: SettingCategory.HEALTH_CHECK,
+ type: EpluginSettingType.switch,
+ defaultValue: true,
+ isConfigurableFromFile: true,
+ isConfigurableFromUI: true,
+ options: {
+ switch: {
+ values: {
+ disabled: { label: 'false', value: false },
+ enabled: { label: 'true', value: true },
+ },
+ },
+ },
+ uiFormTransformChangedInputValue: function (
+ value: boolean | string,
+ ): boolean {
+ return Boolean(value);
+ },
+ validate: SettingsValidator.isBoolean,
+ validateBackend: function (schema) {
+ return schema.boolean();
+ },
+ },
'cron.prefix': {
title: 'Cron prefix',
description: 'Define the index prefix of predefined jobs.',
diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.js
index 24b3b2b421..8c3fd06459 100644
--- a/plugins/main/public/components/common/modules/modules-defaults.js
+++ b/plugins/main/public/components/common/modules/modules-defaults.js
@@ -11,9 +11,7 @@
*/
import { Dashboard } from './dashboard';
import { Events } from './events';
-import { MainFim } from '../../agents/fim';
import { MainSca } from '../../agents/sca';
-import { MainVuls } from '../../agents/vuls';
import { MainMitre } from './main-mitre';
import { ModuleMitreAttackIntelligence } from '../../overview/mitre_attack_intelligence';
import { ComplianceTable } from '../../overview/compliance-table';
@@ -21,8 +19,10 @@ import ButtonModuleExploreAgent from '../../../controllers/overview/components/o
import { ButtonModuleGenerateReport } from '../modules/buttons';
import { OfficePanel } from '../../overview/office-panel';
import { GitHubPanel } from '../../overview/github-panel';
-import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities'
-import { withModuleNotForAgent, withModuleTabLoader } from '../hocs';
+import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities';
+import { withModuleNotForAgent } from '../hocs';
+import { DashboardFim } from '../../overview/fim/dashboard/dashboard';
+import { InventoryFim } from '../../overview/fim/inventory/inventory';
const DashboardTab = {
id: 'dashboard',
@@ -56,12 +56,17 @@ export const ModulesDefaults = {
fim: {
init: 'dashboard',
tabs: [
- DashboardTab,
+ {
+ id: 'dashboard',
+ name: 'Dashboard',
+ buttons: [ButtonModuleExploreAgent],
+ component: DashboardFim,
+ },
{
id: 'inventory',
name: 'Inventory',
buttons: [ButtonModuleExploreAgent],
- component: MainFim,
+ component: InventoryFim,
},
EventsTab,
],
diff --git a/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap b/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap
index 9b6a456c8f..9e6a4d67a0 100644
--- a/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap
+++ b/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap
@@ -122,6 +122,23 @@ exports[`Health Check container should render a Health check screen 1`] = `
title="Check vulnerabilities index pattern"
validationService={[Function]}
/>
+
({
'checks.template': true,
'checks.fields': true,
'checks.vulnerabilities.pattern': true,
+ 'checks.fim.pattern': true,
},
}),
useRootScope: () => ({}),
diff --git a/plugins/main/public/components/health-check/container/health-check.container.tsx b/plugins/main/public/components/health-check/container/health-check.container.tsx
index 29c1252545..f3be74bc46 100644
--- a/plugins/main/public/components/health-check/container/health-check.container.tsx
+++ b/plugins/main/public/components/health-check/container/health-check.container.tsx
@@ -37,6 +37,7 @@ import {
WAZUH_INDEX_TYPE_MONITORING,
WAZUH_INDEX_TYPE_STATISTICS,
WAZUH_INDEX_TYPE_VULNERABILITIES,
+ WAZUH_INDEX_TYPE_FIM,
} from '../../../../common/constants';
import { compose } from 'redux';
@@ -103,6 +104,19 @@ const checks = {
shouldCheck: false,
canRetry: true,
},
+ 'fim.pattern': {
+ title: 'Check fim index pattern',
+ label: 'Fim index pattern',
+ validator: appConfig =>
+ checkPatternSupportService(
+ appConfig.data['fim.pattern'],
+ WAZUH_INDEX_TYPE_FIM,
+ NOT_TIME_FIELD_NAME_INDEX_PATTERN,
+ ),
+ awaitFor: [],
+ shouldCheck: false,
+ canRetry: true,
+ },
};
function HealthCheckComponent() {
diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx b/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx
new file mode 100644
index 0000000000..b13fd11b91
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx
@@ -0,0 +1,108 @@
+import React from 'react';
+import { getPlugins } from '../../../../kibana-services';
+import { ViewMode } from '../../../../../../../src/plugins/embeddable/public';
+import { getDashboardPanels } from './dashboard_panels';
+import { I18nProvider } from '@osd/i18n/react';
+import useSearchBar from '../../../common/search-bar/use-search-bar';
+import { getDashboardFilters } from './dashboard_panels_filters';
+import './fim_filters.scss';
+import { getKPIsPanel } from './dashboard_panels_kpis';
+import { useAppConfig } from '../../../common/hooks';
+
+const plugins = getPlugins();
+
+const SearchBar = getPlugins().data.ui.SearchBar;
+
+const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer;
+
+export const DashboardFim: React.FC = () => {
+ const appConfig = useAppConfig();
+ const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern'];
+
+ const { searchBarProps } = useSearchBar({
+ defaultIndexPatternID: FIM_INDEX_PATTERN_ID,
+ filters: [],
+ });
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts
new file mode 100644
index 0000000000..044622307e
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts
@@ -0,0 +1,682 @@
+import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application';
+import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public';
+
+const getVisStateTopFim = (indexPatternId: string) => {
+ return {
+ id: 'most_detected_fim',
+ title: 'Most detected fim',
+ type: 'horizontal_bar',
+ params: {
+ type: 'histogram',
+ grid: {
+ categoryLines: false,
+ },
+ categoryAxes: [
+ {
+ id: 'CategoryAxis-1',
+ type: 'category',
+ position: 'left',
+ show: true,
+ style: {},
+ scale: {
+ type: 'linear',
+ },
+ labels: {
+ show: true,
+ rotate: 0,
+ filter: false,
+ truncate: 200,
+ },
+ title: {},
+ },
+ ],
+ valueAxes: [
+ {
+ id: 'ValueAxis-1',
+ name: 'LeftAxis-1',
+ type: 'value',
+ position: 'bottom',
+ show: true,
+ style: {},
+ scale: {
+ type: 'linear',
+ mode: 'normal',
+ },
+ labels: {
+ show: true,
+ rotate: 75,
+ filter: true,
+ truncate: 100,
+ },
+ title: {
+ text: 'Count',
+ },
+ },
+ ],
+ seriesParams: [
+ {
+ show: true,
+ type: 'histogram',
+ mode: 'stacked',
+ data: {
+ label: 'Count',
+ id: '1',
+ },
+ valueAxis: 'ValueAxis-1',
+ drawLinesBetweenPoints: true,
+ lineWidth: 2,
+ showCircles: true,
+ },
+ ],
+ addTooltip: true,
+ addLegend: false,
+ legendPosition: 'right',
+ times: [],
+ addTimeMarker: false,
+ labels: {},
+ thresholdLine: {
+ show: false,
+ value: 10,
+ width: 1,
+ style: 'full',
+ color: '#E7664C',
+ },
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {},
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'vulnerability.id',
+ orderBy: '1',
+ order: 'desc',
+ size: 10,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'Vulnerability.ID',
+ },
+ schema: 'segment',
+ },
+ {
+ id: '3',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'vulnerability.id',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ },
+ schema: 'group',
+ },
+ ],
+ },
+ };
+};
+
+const getVisStateTopFimEndpoints = (indexPatternId: string) => {
+ return {
+ id: 'most_vulnerable_endpoints_fim',
+ title: 'The most vulnerable endpoints',
+ type: 'horizontal_bar',
+ params: {
+ type: 'histogram',
+ grid: {
+ categoryLines: false,
+ },
+ categoryAxes: [
+ {
+ id: 'CategoryAxis-1',
+ type: 'category',
+ position: 'left',
+ show: true,
+ style: {},
+ scale: {
+ type: 'linear',
+ },
+ labels: {
+ show: true,
+ rotate: 0,
+ filter: false,
+ truncate: 200,
+ },
+ title: {},
+ },
+ ],
+ valueAxes: [
+ {
+ id: 'ValueAxis-1',
+ name: 'LeftAxis-1',
+ type: 'value',
+ position: 'bottom',
+ show: true,
+ style: {},
+ scale: {
+ type: 'linear',
+ mode: 'normal',
+ },
+ labels: {
+ show: true,
+ rotate: 75,
+ filter: true,
+ truncate: 100,
+ },
+ title: {
+ text: 'Count',
+ },
+ },
+ ],
+ seriesParams: [
+ {
+ show: true,
+ type: 'histogram',
+ mode: 'stacked',
+ data: {
+ label: 'Count',
+ id: '1',
+ },
+ valueAxis: 'ValueAxis-1',
+ drawLinesBetweenPoints: true,
+ lineWidth: 2,
+ showCircles: true,
+ },
+ ],
+ addTooltip: true,
+ addLegend: false,
+ legendPosition: 'right',
+ times: [],
+ addTimeMarker: false,
+ labels: {},
+ thresholdLine: {
+ show: false,
+ value: 10,
+ width: 1,
+ style: 'full',
+ color: '#E7664C',
+ },
+ },
+ uiState: {
+ vis: {
+ legendOpen: false,
+ },
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: 'Count',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'agent.id',
+ orderBy: '1',
+ order: 'desc',
+ size: 10,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'package.path',
+ },
+ schema: 'segment',
+ },
+ {
+ id: '3',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'agent.id',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'mm',
+ },
+ schema: 'group',
+ },
+ ],
+ },
+ };
+};
+
+const getVisStateAccumulationMostDetectedFim = (indexPatternId: string) => {
+ return {
+ id: 'accumulation_most_vulnerable_fim',
+ title: 'Accumulation of the most detected fim',
+ type: 'line',
+ params: {
+ type: 'line',
+ grid: {
+ categoryLines: false,
+ },
+ categoryAxes: [
+ {
+ id: 'CategoryAxis-1',
+ type: 'category',
+ position: 'bottom',
+ show: true,
+ style: {},
+ scale: {
+ type: 'linear',
+ },
+ labels: {
+ show: true,
+ filter: true,
+ truncate: 100,
+ },
+ title: {},
+ },
+ ],
+ valueAxes: [
+ {
+ id: 'ValueAxis-1',
+ name: 'LeftAxis-1',
+ type: 'value',
+ position: 'left',
+ show: true,
+ style: {},
+ scale: {
+ type: 'linear',
+ mode: 'normal',
+ },
+ labels: {
+ show: true,
+ rotate: 0,
+ filter: false,
+ truncate: 100,
+ },
+ title: {
+ text: 'Count',
+ },
+ },
+ ],
+ seriesParams: [
+ {
+ show: true,
+ type: 'line',
+ mode: 'normal',
+ data: {
+ label: 'Count',
+ id: '1',
+ },
+ valueAxis: 'ValueAxis-1',
+ drawLinesBetweenPoints: false,
+ lineWidth: 2,
+ interpolate: 'linear',
+ showCircles: true,
+ },
+ ],
+ addTooltip: true,
+ addLegend: true,
+ legendPosition: 'right',
+ times: [],
+ addTimeMarker: false,
+ labels: {},
+ thresholdLine: {
+ show: false,
+ value: 10,
+ width: 1,
+ style: 'full',
+ color: '#E7664C',
+ },
+ radiusRatio: 20,
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {},
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'count',
+ params: {},
+ schema: 'radius',
+ },
+ {
+ id: '4',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'vulnerability.id',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Others',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ },
+ schema: 'group',
+ },
+ {
+ id: '3',
+ enabled: true,
+ type: 'date_histogram',
+ params: {
+ field: 'event.created',
+ timeRange: {
+ from: 'now-24h',
+ to: 'now',
+ },
+ useNormalizedOpenSearchInterval: true,
+ scaleMetricValues: false,
+ interval: 'w',
+ // eslint-disable-next-line camelcase
+ drop_partials: false,
+ // eslint-disable-next-line camelcase
+ min_doc_count: 1,
+ // eslint-disable-next-line camelcase
+ extended_bounds: {},
+ },
+ schema: 'segment',
+ },
+ ],
+ },
+ };
+};
+
+const getVisStateInventoryTable = (indexPatternId: string) => {
+ return {
+ id: 'inventory_table_fim',
+ title: 'Inventory table',
+ type: 'table',
+ params: {
+ perPage: 5,
+ showPartialRows: false,
+ showMetricsAtAllLevels: false,
+ showTotal: false,
+ totalFunc: 'sum',
+ percentageCol: '',
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: 'Count',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'package.name',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'name',
+ },
+ schema: 'bucket',
+ },
+ {
+ id: '3',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'package.version',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'version',
+ },
+ schema: 'bucket',
+ },
+ {
+ id: '4',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'package.architecture',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'architecture',
+ },
+ schema: 'bucket',
+ },
+ {
+ id: '5',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'vulnerability.severity',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'severity',
+ },
+ schema: 'bucket',
+ },
+ {
+ id: '6',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'vulnerability.id',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'id',
+ },
+ schema: 'bucket',
+ },
+ {
+ id: '7',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'vulnerability.score.version',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'score version',
+ },
+ schema: 'bucket',
+ },
+ {
+ id: '8',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: 'vulnerability.score.base',
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: 'score base',
+ },
+ schema: 'bucket',
+ },
+ ],
+ },
+ };
+};
+
+export const getDashboardPanels = (
+ indexPatternId: string,
+): {
+ [panelId: string]: DashboardPanelState<
+ EmbeddableInput & { [k: string]: unknown }
+ >;
+} => {
+ return {
+ '6': {
+ gridData: {
+ w: 16,
+ h: 12,
+ x: 0,
+ y: 0,
+ i: '6',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '6',
+ savedVis: getVisStateTopFim(indexPatternId),
+ },
+ },
+ '7': {
+ gridData: {
+ w: 16,
+ h: 12,
+ x: 16,
+ y: 0,
+ i: '7',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '7',
+ savedVis: getVisStateTopFimEndpoints(indexPatternId),
+ },
+ },
+ '8': {
+ gridData: {
+ w: 16,
+ h: 12,
+ x: 32,
+ y: 0,
+ i: '8',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '8',
+ savedVis: getVisStateAccumulationMostDetectedFim(indexPatternId),
+ },
+ },
+ '9': {
+ gridData: {
+ w: 48,
+ h: 12,
+ x: 0,
+ y: 14,
+ i: '9',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '9',
+ savedVis: getVisStateInventoryTable(indexPatternId),
+ },
+ },
+ };
+};
diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts
new file mode 100644
index 0000000000..47a6d63ebb
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts
@@ -0,0 +1,160 @@
+import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application';
+import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public';
+
+const getVisStateFilter = (
+ id: string,
+ indexPatternId: string,
+ title: string,
+ label: string,
+ fieldName: string,
+) => {
+ return {
+ id,
+ title,
+ type: 'table',
+ params: {
+ perPage: 5,
+ percentageCol: '',
+ row: true,
+ showMetricsAtAllLevels: false,
+ showPartialRows: false,
+ showTotal: false,
+ totalFunc: 'sum',
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: 'Count',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'terms',
+ params: {
+ field: fieldName,
+ orderBy: '1',
+ order: 'desc',
+ size: 5,
+ otherBucket: false,
+ otherBucketLabel: 'Other',
+ missingBucket: false,
+ missingBucketLabel: 'Missing',
+ customLabel: label,
+ },
+ schema: 'bucket',
+ },
+ ],
+ },
+ };
+};
+
+export const getDashboardFilters = (
+ indexPatternId: string,
+): {
+ [panelId: string]: DashboardPanelState<
+ EmbeddableInput & { [k: string]: unknown }
+ >;
+} => {
+ return {
+ topPackageSelector: {
+ gridData: {
+ w: 12,
+ h: 12,
+ x: 0,
+ y: 0,
+ i: 'topPackageSelector',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: 'topPackageSelector',
+ savedVis: getVisStateFilter(
+ 'topPackageSelector',
+ indexPatternId,
+ 'Top Packages fim',
+ 'Package',
+ 'package.name',
+ ),
+ },
+ },
+ topOSFim: {
+ gridData: {
+ w: 12,
+ h: 12,
+ x: 12,
+ y: 0,
+ i: 'topOSFim',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: 'topOSFim',
+ savedVis: getVisStateFilter(
+ 'topOSFim',
+ indexPatternId,
+ 'Top Operating system fim',
+ 'Operating system',
+ 'host.os.name',
+ ),
+ },
+ },
+ topAgentFim: {
+ gridData: {
+ w: 12,
+ h: 12,
+ x: 24,
+ y: 0,
+ i: 'topAgentFim',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: 'topAgentFim',
+ savedVis: getVisStateFilter(
+ 'topAgentFim',
+ indexPatternId,
+ 'Agent filter',
+ 'Agent',
+ 'agent.id',
+ ),
+ },
+ },
+ topFim: {
+ gridData: {
+ w: 12,
+ h: 12,
+ x: 36,
+ y: 0,
+ i: 'topFim',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: 'topFim',
+ savedVis: getVisStateFilter(
+ 'topFim',
+ indexPatternId,
+ 'Top vulnerabilities',
+ 'Fim',
+ 'fim.id',
+ ),
+ },
+ },
+ };
+};
diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts
new file mode 100644
index 0000000000..99afc7f18f
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts
@@ -0,0 +1,416 @@
+import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application';
+import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public';
+
+const getVisStateSeverityCritical = (indexPatternId: string) => {
+ return {
+ id: 'severity_critical_fim',
+ title: 'Critical',
+ type: 'metric',
+ params: {
+ addTooltip: true,
+ addLegend: false,
+ type: 'metric',
+ metric: {
+ percentageMode: false,
+ useRanges: false,
+ colorSchema: 'Reds',
+ metricColorMode: 'Labels',
+ colorsRange: [
+ {
+ from: 0,
+ to: 0,
+ },
+ {
+ from: 0,
+ to: 0,
+ },
+ ],
+ labels: {
+ show: true,
+ },
+ invertColors: false,
+ style: {
+ bgFill: '#000',
+ bgColor: false,
+ labelColor: false,
+ subText: '',
+ fontSize: 50,
+ },
+ },
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: ' ',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'filters',
+ params: {
+ filters: [
+ {
+ input: {
+ query: 'vulnerability.severity:"critical"',
+ language: 'kuery',
+ },
+ label: '- Critical Severity Alerts',
+ },
+ ],
+ },
+ schema: 'group',
+ },
+ ],
+ },
+ };
+};
+
+const getVisStateSeverityHigh = (indexPatternId: string) => {
+ return {
+ id: 'severity_high_fim',
+ title: 'High',
+ type: 'metric',
+ params: {
+ addTooltip: true,
+ addLegend: false,
+ type: 'metric',
+ metric: {
+ percentageMode: false,
+ useRanges: false,
+ colorSchema: 'Blues',
+ metricColorMode: 'Labels',
+ colorsRange: [
+ {
+ from: 0,
+ to: 0,
+ },
+ {
+ from: 0,
+ to: 0,
+ },
+ ],
+ labels: {
+ show: true,
+ },
+ invertColors: false,
+ style: {
+ bgFill: '#000',
+ bgColor: false,
+ labelColor: false,
+ subText: '',
+ fontSize: 50,
+ },
+ },
+ },
+ uiState: {
+ vis: {
+ colors: {
+ 'High Severity Alerts - Count': '#38D1BA',
+ },
+ },
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: ' ',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'filters',
+ params: {
+ filters: [
+ {
+ input: {
+ query: 'vulnerability.severity:"high"',
+ language: 'kuery',
+ },
+ label: '- High Severity Alerts',
+ },
+ ],
+ },
+ schema: 'group',
+ },
+ ],
+ },
+ };
+};
+
+const getVisStateSeverityMedium = (indexPatternId: string) => {
+ return {
+ id: 'severity_medium_fim',
+ title: 'Medium',
+ type: 'metric',
+ params: {
+ addTooltip: true,
+ addLegend: false,
+ type: 'metric',
+ metric: {
+ percentageMode: false,
+ useRanges: false,
+ colorSchema: 'Yellow to Red',
+ metricColorMode: 'Labels',
+ colorsRange: [
+ {
+ from: 0,
+ to: 0,
+ },
+ {
+ from: 0,
+ to: 0,
+ },
+ ],
+ labels: {
+ show: true,
+ },
+ invertColors: true,
+ style: {
+ bgFill: '#000',
+ bgColor: false,
+ labelColor: false,
+ subText: '',
+ fontSize: 50,
+ },
+ },
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: ' ',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'filters',
+ params: {
+ filters: [
+ {
+ input: {
+ query: 'vulnerability.severity:"medium"',
+ language: 'kuery',
+ },
+ label: '- Medium Severity Alerts',
+ },
+ ],
+ },
+ schema: 'group',
+ },
+ ],
+ },
+ };
+};
+
+const getVisStateSeverityLow = (indexPatternId: string) => {
+ return {
+ id: 'severity_low_fim',
+ title: 'Low',
+ type: 'metric',
+ params: {
+ addTooltip: true,
+ addLegend: false,
+ type: 'metric',
+ metric: {
+ percentageMode: false,
+ useRanges: false,
+ colorSchema: 'Greens',
+ metricColorMode: 'Labels',
+ colorsRange: [
+ {
+ from: 0,
+ to: 0,
+ },
+ {
+ from: 0,
+ to: 0,
+ },
+ ],
+ labels: {
+ show: true,
+ },
+ invertColors: false,
+ style: {
+ bgFill: '#000',
+ bgColor: false,
+ labelColor: false,
+ subText: '',
+ fontSize: 50,
+ },
+ },
+ },
+ data: {
+ searchSource: {
+ query: {
+ language: 'kuery',
+ query: '',
+ },
+ filter: [],
+ index: indexPatternId,
+ },
+ references: [
+ {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ type: 'index-pattern',
+ id: indexPatternId,
+ },
+ ],
+ aggs: [
+ {
+ id: '1',
+ enabled: true,
+ type: 'count',
+ params: {
+ customLabel: ' ',
+ },
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'filters',
+ params: {
+ filters: [
+ {
+ input: {
+ query: 'vulnerability.severity:"low"',
+ language: 'kuery',
+ },
+ label: '- Low Severity Alerts',
+ },
+ ],
+ },
+ schema: 'group',
+ },
+ ],
+ },
+ };
+};
+
+export const getKPIsPanel = (
+ indexPatternId: string,
+): {
+ [panelId: string]: DashboardPanelState<
+ EmbeddableInput & { [k: string]: unknown }
+ >;
+} => {
+ return {
+ '1': {
+ gridData: {
+ w: 12,
+ h: 6,
+ x: 0,
+ y: 0,
+ i: '1',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '1',
+ savedVis: getVisStateSeverityCritical(indexPatternId),
+ },
+ },
+ '2': {
+ gridData: {
+ w: 12,
+ h: 6,
+ x: 12,
+ y: 0,
+ i: '2',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '2',
+ savedVis: getVisStateSeverityHigh(indexPatternId),
+ },
+ },
+ '3': {
+ gridData: {
+ w: 12,
+ h: 6,
+ x: 24,
+ y: 0,
+ i: '3',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '3',
+ savedVis: getVisStateSeverityMedium(indexPatternId),
+ },
+ },
+ '4': {
+ gridData: {
+ w: 12,
+ h: 6,
+ x: 36,
+ y: 0,
+ i: '4',
+ },
+ type: 'visualization',
+ explicitInput: {
+ id: '4',
+ savedVis: getVisStateSeverityLow(indexPatternId),
+ },
+ },
+ };
+};
diff --git a/plugins/main/public/components/overview/fim/dashboard/fim_filters.scss b/plugins/main/public/components/overview/fim/dashboard/fim_filters.scss
new file mode 100644
index 0000000000..a836b86e3e
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/fim_filters.scss
@@ -0,0 +1,6 @@
+.fim-dashboard-filters-wrapper {
+ .euiDataGrid__controls,
+ .euiDataGrid__pagination {
+ display: none !important;
+ }
+}
diff --git a/plugins/main/public/components/overview/fim/dashboard/index.tsx b/plugins/main/public/components/overview/fim/dashboard/index.tsx
new file mode 100644
index 0000000000..b691822976
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/index.tsx
@@ -0,0 +1 @@
+export * from './dashboard';
\ No newline at end of file
diff --git a/plugins/main/public/components/overview/fim/inventory/config/index.ts b/plugins/main/public/components/overview/fim/inventory/config/index.ts
new file mode 100644
index 0000000000..aa5490ffd8
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/inventory/config/index.ts
@@ -0,0 +1,30 @@
+import { EuiDataGridColumn } from '@elastic/eui';
+
+export const MAX_ENTRIES_PER_QUERY = 10000;
+
+export const inventoryTableDefaultColumns: EuiDataGridColumn[] = [
+ {
+ id: 'package.name',
+ },
+ {
+ id: 'package.version',
+ },
+ {
+ id: 'package.architecture',
+ },
+ {
+ id: 'fim.severity',
+ },
+ {
+ id: 'fim.id',
+ },
+ {
+ id: 'fim.score.version',
+ },
+ {
+ id: 'fim.score.base',
+ },
+ {
+ id: 'event.created',
+ },
+];
diff --git a/plugins/main/public/components/overview/fim/inventory/index.tsx b/plugins/main/public/components/overview/fim/inventory/index.tsx
new file mode 100644
index 0000000000..ddb0742f5e
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/inventory/index.tsx
@@ -0,0 +1 @@
+export * from './inventory';
\ No newline at end of file
diff --git a/plugins/main/public/components/overview/fim/inventory/inventory.scss b/plugins/main/public/components/overview/fim/inventory/inventory.scss
new file mode 100644
index 0000000000..2303093ed2
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/inventory/inventory.scss
@@ -0,0 +1,13 @@
+.fimInventoryContainer {
+ height: calc(100vh - 104px);
+}
+
+.headerIsExpanded .fimInventoryContainer {
+ height: calc(100vh - 153px);
+}
+
+.fimInventoryContainer .euiDataGrid--fullScreen {
+ height: calc(100vh - 49px);
+ bottom: 0;
+ top: auto;
+}
diff --git a/plugins/main/public/components/overview/fim/inventory/inventory.tsx b/plugins/main/public/components/overview/fim/inventory/inventory.tsx
new file mode 100644
index 0000000000..39503b0e94
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/inventory/inventory.tsx
@@ -0,0 +1,244 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { getPlugins } from '../../../../kibana-services';
+import useSearchBar from '../../../common/search-bar/use-search-bar';
+import { IntlProvider } from 'react-intl';
+import {
+ EuiDataGrid,
+ EuiPageTemplate,
+ EuiToolTip,
+ EuiButtonIcon,
+ EuiDataGridCellValueElementProps,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiButtonEmpty,
+} from '@elastic/eui';
+import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
+import { SearchResponse } from '../../../../../../../../src/core/server';
+import DocViewer from '../../vulnerabilities/doc_viewer/doc_viewer';
+import { DiscoverNoResults } from '../../vulnerabilities/common/components/no_results';
+import { LoadingSpinner } from '../../vulnerabilities/common/components/loading_spinner';
+import { useDataGrid } from '../../vulnerabilities/data_grid/use_data_grid';
+import {
+ MAX_ENTRIES_PER_QUERY,
+ inventoryTableDefaultColumns,
+} from '../../vulnerabilities/dashboards/inventory/config';
+import { useDocViewer } from '../../vulnerabilities/doc_viewer/use_doc_viewer';
+import './inventory.scss';
+import {
+ search,
+ exportSearchToCSV,
+} from '../../vulnerabilities/dashboards/inventory/inventory_service';
+import {
+ ErrorHandler,
+ ErrorFactory,
+ HttpError,
+} from '../../../../react-services/error-management';
+import { withErrorBoundary } from '../../../common/hocs';
+import { HitsCounter } from '../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter';
+import { formatNumWithCommas } from '../../../../kibana-integrations/discover/application/helpers';
+import { useAppConfig } from '../../../common/hooks';
+
+const InventoryFimComponent = () => {
+ const appConfig = useAppConfig();
+ const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern'];
+ const { searchBarProps } = useSearchBar({
+ defaultIndexPatternID: FIM_INDEX_PATTERN_ID,
+ });
+ const { isLoading, filters, query, indexPatterns } = searchBarProps;
+ const SearchBar = getPlugins().data.ui.SearchBar;
+ const [results, setResults] = useState({} as SearchResponse);
+ const [inspectedHit, setInspectedHit] = useState(undefined);
+ const [indexPattern, setIndexPattern] = useState(
+ undefined,
+ );
+ const [isSearching, setIsSearching] = useState(false);
+ const [isExporting, setIsExporting] = useState(false);
+
+ const onClickInspectDoc = useMemo(
+ () => (index: number) => {
+ const rowClicked = results.hits.hits[index];
+ setInspectedHit(rowClicked);
+ },
+ [results],
+ );
+
+ const DocViewInspectButton = ({
+ rowIndex,
+ }: EuiDataGridCellValueElementProps) => {
+ const inspectHintMsg = 'Inspect document details';
+ return (
+
+ onClickInspectDoc(rowIndex)}
+ iconType='inspect'
+ aria-label={inspectHintMsg}
+ />
+
+ );
+ };
+
+ const dataGridProps = useDataGrid({
+ ariaLabelledBy: 'Fim Inventory Table',
+ defaultColumns: inventoryTableDefaultColumns,
+ results,
+ indexPattern: indexPattern as IndexPattern,
+ DocViewInspectButton,
+ });
+
+ const { pagination, sorting, columnVisibility } = dataGridProps;
+
+ const docViewerProps = useDocViewer({
+ doc: inspectedHit,
+ indexPattern: indexPattern as IndexPattern,
+ });
+
+ useEffect(() => {
+ if (!isLoading) {
+ setIndexPattern(indexPatterns?.[0] as IndexPattern);
+ search({
+ indexPattern: indexPatterns?.[0] as IndexPattern,
+ filters,
+ query,
+ pagination,
+ sorting,
+ })
+ .then(results => {
+ setResults(results);
+ setIsSearching(false);
+ })
+ .catch(error => {
+ const searchError = ErrorFactory.create(HttpError, {
+ error,
+ message: 'Error fetching fim',
+ });
+ ErrorHandler.handleError(searchError);
+ setIsSearching(false);
+ });
+ }
+ }, [
+ JSON.stringify(searchBarProps),
+ JSON.stringify(pagination),
+ JSON.stringify(sorting),
+ ]);
+
+ const timeField = indexPattern?.timeFieldName
+ ? indexPattern.timeFieldName
+ : undefined;
+
+ const onClickExportResults = async () => {
+ const params = {
+ indexPattern: indexPatterns?.[0] as IndexPattern,
+ filters,
+ query,
+ fields: columnVisibility.visibleColumns,
+ pagination: {
+ pageIndex: 0,
+ pageSize: results.hits.total,
+ },
+ sorting,
+ };
+ try {
+ setIsExporting(true);
+ await exportSearchToCSV(params);
+ } catch (error) {
+ const searchError = ErrorFactory.create(HttpError, {
+ error,
+ message: 'Error downloading csv report',
+ });
+ ErrorHandler.handleError(searchError);
+ } finally {
+ setIsExporting(false);
+ }
+ };
+
+ return (
+
+
+ <>
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ {isSearching ? : null}
+ {!isLoading && !isSearching && results?.hits?.total === 0 ? (
+
+ ) : null}
+ {!isLoading && !isSearching && results?.hits?.total > 0 ? (
+
+ {}}
+ tooltip={
+ results?.hits?.total &&
+ results?.hits?.total > MAX_ENTRIES_PER_QUERY
+ ? {
+ ariaLabel: 'Warning',
+ content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas(
+ MAX_ENTRIES_PER_QUERY,
+ )} hits.`,
+ iconType: 'alert',
+ position: 'top',
+ }
+ : undefined
+ }
+ />
+
+ Export Formated
+
+ >
+ ),
+ }}
+ />
+ ) : null}
+ {inspectedHit && (
+ setInspectedHit(undefined)} size='m'>
+
+
+ Document Details
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+
+
+ );
+};
+
+export const InventoryFim = withErrorBoundary(InventoryFimComponent);
diff --git a/plugins/main/public/components/overview/fim/inventory/inventory_service.ts b/plugins/main/public/components/overview/fim/inventory/inventory_service.ts
new file mode 100644
index 0000000000..82ef36b007
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/inventory/inventory_service.ts
@@ -0,0 +1,224 @@
+import { SearchResponse } from '../../../../../../../../src/core/server';
+import { getPlugins } from '../../../../kibana-services';
+import {
+ IndexPattern,
+ Filter,
+ OpenSearchQuerySortValue,
+} from '../../../../../../../../src/plugins/data/public';
+import * as FileSaver from '../../../../../services/file-saver';
+import { beautifyDate } from '../../../../agents/vuls/inventory/lib';
+import { MAX_ENTRIES_PER_QUERY } from './config';
+
+interface SearchParams {
+ indexPattern: IndexPattern;
+ filters?: Filter[];
+ query?: any;
+ pagination?: {
+ pageIndex?: number;
+ pageSize?: number;
+ };
+ fields?: string[];
+ sorting?: {
+ columns: {
+ id: string;
+ direction: 'asc' | 'desc';
+ }[];
+ };
+}
+
+export const search = async (
+ params: SearchParams,
+): Promise => {
+ const {
+ indexPattern,
+ filters = [],
+ query,
+ pagination,
+ sorting,
+ fields,
+ } = params;
+ if (!indexPattern) {
+ return;
+ }
+ const data = getPlugins().data;
+ const searchSource = await data.search.searchSource.create();
+ const fromField =
+ (pagination?.pageIndex || 0) * (pagination?.pageSize || 100);
+ const sortOrder: OpenSearchQuerySortValue[] =
+ sorting?.columns.map(column => {
+ const sortDirection = column.direction === 'asc' ? 'asc' : 'desc';
+ return { [column?.id || '']: sortDirection } as OpenSearchQuerySortValue;
+ }) || [];
+
+ const searchParams = searchSource
+ .setParent(undefined)
+ .setField('filter', filters)
+ .setField('query', query)
+ .setField('sort', sortOrder)
+ .setField('size', pagination?.pageSize)
+ .setField('from', fromField)
+ .setField('index', indexPattern);
+
+ // add fields
+ if (fields && Array.isArray(fields) && fields.length > 0) {
+ searchParams.setField('fields', fields);
+ }
+ try {
+ return await searchParams.fetch();
+ } catch (error) {
+ if (error.body) {
+ throw error.body;
+ }
+ throw error;
+ }
+};
+
+export const parseData = (
+ resultsHits: SearchResponse['hits']['hits'],
+): any[] => {
+ const data = resultsHits.map(hit => {
+ if (!hit) {
+ return {};
+ }
+ const source = hit._source as object;
+ const data = {
+ ...source,
+ _id: hit._id,
+ _index: hit._index,
+ _type: hit._type,
+ _score: hit._score,
+ };
+ return data;
+ });
+ return data;
+};
+
+export const getFieldFormatted = (
+ rowIndex,
+ columnId,
+ indexPattern,
+ rowsParsed,
+) => {
+ const field = indexPattern.fields.find(field => field.name === columnId);
+ let fieldValue = null;
+ if (columnId.includes('.')) {
+ // when the column is a nested field. The column could have 2 to n levels
+ // get dinamically the value of the nested field
+ const nestedFields = columnId.split('.');
+ fieldValue = rowsParsed[rowIndex];
+ nestedFields.forEach(field => {
+ if (fieldValue) {
+ fieldValue = fieldValue[field];
+ }
+ });
+ } else {
+ fieldValue = rowsParsed[rowIndex][columnId].formatted
+ ? rowsParsed[rowIndex][columnId].formatted
+ : rowsParsed[rowIndex][columnId];
+ }
+
+ // if is date field
+ if (field?.type === 'date') {
+ // @ts-ignore
+ fieldValue = beautifyDate(fieldValue);
+ }
+ return fieldValue;
+};
+
+export const exportSearchToCSV = async (
+ params: SearchParams,
+): Promise => {
+ const DEFAULT_MAX_SIZE_PER_CALL = 1000;
+ const {
+ indexPattern,
+ filters = [],
+ query,
+ sorting,
+ fields,
+ pagination,
+ } = params;
+ // when the pageSize is greater than the default max size per call (10000)
+ // then we need to paginate the search
+ const mustPaginateSearch =
+ pagination?.pageSize && pagination?.pageSize > DEFAULT_MAX_SIZE_PER_CALL;
+ const pageSize = mustPaginateSearch
+ ? DEFAULT_MAX_SIZE_PER_CALL
+ : pagination?.pageSize;
+ const totalHits = pagination?.pageSize || DEFAULT_MAX_SIZE_PER_CALL;
+ let pageIndex = params.pagination?.pageIndex || 0;
+ let hitsCount = 0;
+ let allHits = [];
+ let searchResults;
+ if (mustPaginateSearch) {
+ // paginate the search
+ while (hitsCount < totalHits && hitsCount < MAX_ENTRIES_PER_QUERY) {
+ const searchParams = {
+ indexPattern,
+ filters,
+ query,
+ pagination: {
+ pageIndex,
+ pageSize,
+ },
+ sorting,
+ fields,
+ };
+ searchResults = await search(searchParams);
+ allHits = allHits.concat(searchResults.hits.hits);
+ hitsCount = allHits.length;
+ pageIndex++;
+ }
+ } else {
+ searchResults = await search(params);
+ allHits = searchResults.hits.hits;
+ }
+
+ const resultsFields = fields;
+ const data = allHits.map(hit => {
+ // check if the field type is a date
+ const dateFields = indexPattern.fields.getByType('date');
+ const dateFieldsNames = dateFields.map(field => field.name);
+ const flattenHit = indexPattern.flattenHit(hit);
+ // replace the date fields with the formatted date
+ dateFieldsNames.forEach(field => {
+ if (flattenHit[field]) {
+ flattenHit[field] = beautifyDate(flattenHit[field]);
+ }
+ });
+ return flattenHit;
+ });
+
+ if (!resultsFields || resultsFields.length === 0) {
+ return;
+ }
+
+ if (!data || data.length === 0) return;
+
+ const parsedData = data
+ .map(row => {
+ const parsedRow = resultsFields?.map(field => {
+ const value = row[field];
+ if (value === undefined || value === null) {
+ return '';
+ }
+ if (typeof value === 'object') {
+ return JSON.stringify(value);
+ }
+ return `"${value}"`;
+ });
+ return parsedRow?.join(',');
+ })
+ .join('\n');
+
+ // create a csv file using blob
+ const blobData = new Blob([`${resultsFields?.join(',')}\n${parsedData}`], {
+ type: 'text/csv',
+ });
+
+ if (blobData) {
+ FileSaver?.saveAs(
+ blobData,
+ `fim_inventory-${new Date().toISOString()}.csv`,
+ );
+ }
+};
diff --git a/plugins/main/public/redux/reducers/appConfigReducers.ts b/plugins/main/public/redux/reducers/appConfigReducers.ts
index 5f3d43fd92..a987ce70e9 100644
--- a/plugins/main/public/redux/reducers/appConfigReducers.ts
+++ b/plugins/main/public/redux/reducers/appConfigReducers.ts
@@ -18,12 +18,15 @@ const initialState: AppConfigState = {
isLoading: false,
isReady: false,
hasError: false,
- data: getSettingsDefault(),
+ data: {
+ 'vulnerabilities.pattern': 'wazuh-states-vulnerabilities',
+ 'fim.pattern': 'wazuh-states-fim',
+ },
};
const appConfigReducer: Reducer = (
state = initialState,
- action
+ action,
) => {
switch (action.type) {
case 'UPDATE_APP_CONFIG_SET_IS_LOADING':
@@ -31,22 +34,30 @@ const appConfigReducer: Reducer = (
...state,
isLoading: true,
isReady: false,
- hasError: false
+ hasError: false,
};
case 'UPDATE_APP_CONFIG_SET_HAS_ERROR':
- return {
- ...state,
- isLoading: false,
- isReady: false,
- hasError: true
- };
+ return {
+ ...state,
+ isLoading: false,
+ isReady: false,
+ hasError: true,
+ };
case 'UPDATE_APP_CONFIG_DATA':
return {
...state,
isLoading: false,
isReady: true,
hasError: false,
- data: {...state.data, ...action.payload},
+ data: { ...state.data, ...action.payload },
+ };
+ case 'UPDATE_FIM_PATTERN':
+ return {
+ ...state,
+ data: {
+ ...state.data,
+ 'fim.pattern': action.payload,
+ },
};
default:
return state;
diff --git a/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json b/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json
index 5f23722d0f..5a4d7878de 100644
--- a/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json
+++ b/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json
@@ -99,6 +99,11 @@
"title": "Vulnerabilities index pattern",
"subTitle": "Enable or disable the vulnerabilities index pattern health check when opening the app.",
"label": "checks.vulnerabilities.pattern"
+ },
+ {
+ "title": "Fim index pattern",
+ "subTitle": "Enable or disable the fim index pattern health check when opening the app.",
+ "label": "checks.fim.pattern"
}
]
},
diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts
index 4949a30685..180f6679dc 100644
--- a/plugins/wazuh-core/common/constants.ts
+++ b/plugins/wazuh-core/common/constants.ts
@@ -861,6 +861,33 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = {
return schema.boolean();
},
},
+ 'checks.fim.pattern': {
+ title: 'Fim index pattern',
+ description:
+ 'Enable or disable the fim index pattern health check when opening the app.',
+ category: SettingCategory.HEALTH_CHECK,
+ type: EpluginSettingType.switch,
+ defaultValue: true,
+ isConfigurableFromFile: true,
+ isConfigurableFromUI: true,
+ options: {
+ switch: {
+ values: {
+ disabled: { label: 'false', value: false },
+ enabled: { label: 'true', value: true },
+ },
+ },
+ },
+ uiFormTransformChangedInputValue: function (
+ value: boolean | string,
+ ): boolean {
+ return Boolean(value);
+ },
+ validate: SettingsValidator.isBoolean,
+ validateBackend: function (schema) {
+ return schema.boolean();
+ },
+ },
'cron.prefix': {
title: 'Cron prefix',
description: 'Define the index prefix of predefined jobs.',