(a.label > b.label ? 1 : -1);
+
+export const NetworkInterfacesTable = ({ agent }) => {
+ return (
+
+ field)
+ .join(',')}`}
+ searchTable
+ downloadCsv
+ showReload
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ searchBarWQL={{
+ suggestions: {
+ field(currentValue) {
+ return netifaceColumns
+ .map(item => ({
+ label: item.field,
+ description: `filter by ${item.name}`,
+ }))
+ .sort(sortFieldSuggestion);
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/syscollector/${agent.id}/netiface`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ tableProps={{
+ tableLayout: 'auto',
+ }}
+ />
+
+ );
+};
diff --git a/plugins/main/public/components/agents/syscollector/components/network-ports-table.tsx b/plugins/main/public/components/agents/syscollector/components/network-ports-table.tsx
new file mode 100644
index 0000000000..0d1f43aa73
--- /dev/null
+++ b/plugins/main/public/components/agents/syscollector/components/network-ports-table.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { EuiFlexItem, EuiFlexGroup, EuiIcon, EuiPanel } from '@elastic/eui';
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants';
+import { TableWzAPI } from '../../../common/tables';
+import { WzRequest } from '../../../../react-services';
+import { get as getLodash } from 'lodash';
+import { portsColumns } from '../columns';
+import { withSOPlatformGuard } from './with-so-platform-guard';
+
+const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1);
+
+export const NetworkPortsTable = withSOPlatformGuard(
+ ({ agent, soPlatform }) => {
+ return (
+
+ field)
+ .join(',')}`}
+ searchTable
+ downloadCsv
+ showReload
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ searchBarWQL={{
+ suggestions: {
+ field(currentValue) {
+ return portsColumns[soPlatform]
+ .map(item => ({
+ label: item.field,
+ description: `filter by ${item.name}`,
+ }))
+ .sort(sortFieldSuggestion);
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/syscollector/${agent.id}/ports`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ tableProps={{
+ tableLayout: 'auto',
+ }}
+ />
+
+ );
+ },
+);
diff --git a/plugins/main/public/components/agents/syscollector/components/network-settings-table.tsx b/plugins/main/public/components/agents/syscollector/components/network-settings-table.tsx
new file mode 100644
index 0000000000..a6436086e7
--- /dev/null
+++ b/plugins/main/public/components/agents/syscollector/components/network-settings-table.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import { EuiPanel } from '@elastic/eui';
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants';
+import { TableWzAPI } from '../../../common/tables';
+import { WzRequest } from '../../../../react-services';
+import { get as getLodash } from 'lodash';
+import { netaddrColumns } from '../columns';
+
+const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1);
+
+export const NetworkSettingsTable = ({ agent }) => {
+ return (
+
+ field)
+ .join(',')}`}
+ searchTable
+ downloadCsv
+ showReload
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ searchBarWQL={{
+ suggestions: {
+ field(currentValue) {
+ return netaddrColumns
+ .map(item => ({
+ label: item.field,
+ description: `filter by ${item.name}`,
+ }))
+ .sort(sortFieldSuggestion);
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/syscollector/${agent.id}/netaddr`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ tableProps={{
+ tableLayout: 'auto',
+ }}
+ />
+
+ );
+};
diff --git a/plugins/main/public/components/agents/syscollector/components/packages-table.tsx b/plugins/main/public/components/agents/syscollector/components/packages-table.tsx
new file mode 100644
index 0000000000..087aae5ba5
--- /dev/null
+++ b/plugins/main/public/components/agents/syscollector/components/packages-table.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { EuiIcon, EuiPanel } from '@elastic/eui';
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants';
+import { TableWzAPI } from '../../../common/tables';
+import { WzRequest } from '../../../../react-services';
+import { get as getLodash } from 'lodash';
+import { packagesColumns } from '../columns';
+import { withSOPlatformGuard } from './with-so-platform-guard';
+
+const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1);
+
+export const PackagesTable = withSOPlatformGuard(({ agent, soPlatform }) => {
+ return (
+
+ field)
+ .join(',')}`}
+ searchTable
+ downloadCsv
+ showReload
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ searchBarWQL={{
+ suggestions: {
+ field(currentValue) {
+ return packagesColumns[soPlatform]
+ .map(item => ({
+ label: item.field,
+ description: `filter by ${item.name}`,
+ }))
+ .sort(sortFieldSuggestion);
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/syscollector/${agent.id}/packages`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ tableProps={{
+ tableLayout: 'auto',
+ }}
+ />
+
+ );
+});
diff --git a/plugins/main/public/components/agents/syscollector/components/processes-table.tsx b/plugins/main/public/components/agents/syscollector/components/processes-table.tsx
new file mode 100644
index 0000000000..ea571673c3
--- /dev/null
+++ b/plugins/main/public/components/agents/syscollector/components/processes-table.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { EuiIcon, EuiPanel } from '@elastic/eui';
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants';
+import { TableWzAPI } from '../../../common/tables';
+import { WzRequest } from '../../../../react-services';
+import { get as getLodash } from 'lodash';
+import { processColumns } from '../columns';
+import { withSOPlatformGuard } from './with-so-platform-guard';
+
+const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1);
+
+export const ProcessesTable = withSOPlatformGuard(({ agent, soPlatform }) => {
+ return (
+
+ field)
+ .join(',')}`}
+ searchTable
+ downloadCsv
+ showReload
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ searchBarWQL={{
+ suggestions: {
+ field(currentValue) {
+ return processColumns[soPlatform]
+ .map(item => ({
+ label: item.field,
+ description: `filter by ${item.name}`,
+ }))
+ .sort(sortFieldSuggestion);
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/syscollector/${agent.id}/processes`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ tableProps={{
+ tableLayout: 'auto',
+ }}
+ />
+
+ );
+});
diff --git a/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx
new file mode 100644
index 0000000000..585726b3a8
--- /dev/null
+++ b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import { EuiPanel } from '@elastic/eui';
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../common/constants';
+import { TableWzAPI } from '../../../common/tables';
+import { WzRequest } from '../../../../react-services';
+import { get as getLodash } from 'lodash';
+import { windowsUpdatesColumns } from '../columns';
+
+const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1);
+
+export const WindowsUpdatesTable = ({ agent }) => {
+ return (
+
+ field)
+ .join(',')}`}
+ searchTable
+ downloadCsv
+ showReload
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ searchBarWQL={{
+ suggestions: {
+ field(currentValue) {
+ return windowsUpdatesColumns
+ .map(item => ({
+ label: item.field,
+ description: `filter by ${item.name}`,
+ }))
+ .sort(sortFieldSuggestion);
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/syscollector/${agent.id}/hotfixes`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ tableProps={{
+ tableLayout: 'auto',
+ }}
+ />
+
+ );
+};
diff --git a/plugins/main/public/components/agents/syscollector/components/with-so-platform-guard.tsx b/plugins/main/public/components/agents/syscollector/components/with-so-platform-guard.tsx
new file mode 100644
index 0000000000..bf3efd3b17
--- /dev/null
+++ b/plugins/main/public/components/agents/syscollector/components/with-so-platform-guard.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { EuiFlexItem, EuiFlexGroup, EuiIcon, EuiPanel } from '@elastic/eui';
+
+export const withSOPlatformGuard = WrappedComponent => props => {
+ const { soPlatform } = props;
+ if (!soPlatform) {
+ return (
+
+
+
+
+
+ Not enough hardware or operating system information
+
+
+
+
+ );
+ }
+ return
;
+};
diff --git a/plugins/main/public/components/agents/syscollector/inventory.scss b/plugins/main/public/components/agents/syscollector/inventory.scss
new file mode 100644
index 0000000000..e00bbb455f
--- /dev/null
+++ b/plugins/main/public/components/agents/syscollector/inventory.scss
@@ -0,0 +1,3 @@
+.wz-agent-inventory-panel {
+ margin: '12px 16px 12px 16px';
+}
diff --git a/plugins/main/public/components/agents/syscollector/inventory.tsx b/plugins/main/public/components/agents/syscollector/inventory.tsx
index f6060112e3..57df7132ee 100644
--- a/plugins/main/public/components/agents/syscollector/inventory.tsx
+++ b/plugins/main/public/components/agents/syscollector/inventory.tsx
@@ -11,28 +11,21 @@
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiPanel } from '@elastic/eui';
+import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui';
import { InventoryMetrics } from './components/syscollector-metrics';
-import {
- netaddrColumns,
- netifaceColumns,
- processColumns,
- portsColumns,
- packagesColumns,
- windowsUpdatesColumns,
-} from './columns';
-import {
- API_NAME_AGENT_STATUS,
- SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
-} from '../../../../common/constants';
-import { TableWzAPI } from '../../common/tables';
-import { WzRequest } from '../../../react-services';
-import { get as getLodash } from 'lodash';
+import { API_NAME_AGENT_STATUS } from '../../../../common/constants';
import { compose } from 'redux';
import { withGuard } from '../../common/hocs';
import { PromptAgentNeverConnected } from '../prompts';
-
-const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1);
+import {
+ NetworkInterfacesTable,
+ NetworkPortsTable,
+ NetworkSettingsTable,
+ WindowsUpdatesTable,
+ ProcessesTable,
+ PackagesTable,
+} from './components';
+import './inventory.scss';
export const SyscollectorInventory = compose(
withGuard(
@@ -72,359 +65,33 @@ export const SyscollectorInventory = compose(
-
- field)
- .join(',')}`}
- searchTable
- downloadCsv
- showReload
- tablePageSizeOptions={[10, 25, 50, 100]}
- searchBarWQL={{
- suggestions: {
- field(currentValue) {
- return netifaceColumns
- .map(item => ({
- label: item.field,
- description: `filter by ${item.name}`,
- }))
- .sort(sortFieldSuggestion);
- },
- value: async (currentValue, { field }) => {
- try {
- const response = await WzRequest.apiReq(
- 'GET',
- `/syscollector/${agent.id}/netiface`,
- {
- params: {
- distinct: true,
- limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
- select: field,
- sort: `+${field}`,
- ...(currentValue
- ? { q: `${field}~${currentValue}` }
- : {}),
- },
- },
- );
- return response?.data?.data.affected_items.map(item => ({
- label: getLodash(item, field),
- }));
- } catch (error) {
- return [];
- }
- },
- },
- }}
- tableProps={{
- tableLayout: 'auto',
- }}
- />
-
+
-
- field)
- .join(',')}`}
- searchTable
- downloadCsv
- showReload
- tablePageSizeOptions={[10, 25, 50, 100]}
- searchBarWQL={{
- suggestions: {
- field(currentValue) {
- return portsColumns[soPlatform]
- .map(item => ({
- label: item.field,
- description: `filter by ${item.name}`,
- }))
- .sort(sortFieldSuggestion);
- },
- value: async (currentValue, { field }) => {
- try {
- const response = await WzRequest.apiReq(
- 'GET',
- `/syscollector/${agent.id}/ports`,
- {
- params: {
- distinct: true,
- limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
- select: field,
- sort: `+${field}`,
- ...(currentValue
- ? { q: `${field}~${currentValue}` }
- : {}),
- },
- },
- );
- return response?.data?.data.affected_items.map(item => ({
- label: getLodash(item, field),
- }));
- } catch (error) {
- return [];
- }
- },
- },
- }}
- tableProps={{
- tableLayout: 'auto',
- }}
- />
-
+
-
- field)
- .join(',')}`}
- searchTable
- downloadCsv
- showReload
- tablePageSizeOptions={[10, 25, 50, 100]}
- searchBarWQL={{
- suggestions: {
- field(currentValue) {
- return netaddrColumns
- .map(item => ({
- label: item.field,
- description: `filter by ${item.name}`,
- }))
- .sort(sortFieldSuggestion);
- },
- value: async (currentValue, { field }) => {
- try {
- const response = await WzRequest.apiReq(
- 'GET',
- `/syscollector/${agent.id}/netaddr`,
- {
- params: {
- distinct: true,
- limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
- select: field,
- sort: `+${field}`,
- ...(currentValue
- ? { q: `${field}~${currentValue}` }
- : {}),
- },
- },
- );
- return response?.data?.data.affected_items.map(item => ({
- label: getLodash(item, field),
- }));
- } catch (error) {
- return [];
- }
- },
- },
- }}
- tableProps={{
- tableLayout: 'auto',
- }}
- />
-
+
{agent && agent.os && agent.os.platform === 'windows' && (
-
- field)
- .join(',')}`}
- searchTable
- downloadCsv
- showReload
- tablePageSizeOptions={[10, 25, 50, 100]}
- searchBarWQL={{
- suggestions: {
- field(currentValue) {
- return windowsUpdatesColumns
- .map(item => ({
- label: item.field,
- description: `filter by ${item.name}`,
- }))
- .sort(sortFieldSuggestion);
- },
- value: async (currentValue, { field }) => {
- try {
- const response = await WzRequest.apiReq(
- 'GET',
- `/syscollector/${agent.id}/hotfixes`,
- {
- params: {
- distinct: true,
- limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
- select: field,
- sort: `+${field}`,
- ...(currentValue
- ? { q: `${field}~${currentValue}` }
- : {}),
- },
- },
- );
- return response?.data?.data.affected_items.map(
- item => ({
- label: getLodash(item, field),
- }),
- );
- } catch (error) {
- return [];
- }
- },
- },
- }}
- tableProps={{
- tableLayout: 'auto',
- }}
- />
-
+
)}
-
- field)
- .join(',')}`}
- searchTable
- downloadCsv
- showReload
- tablePageSizeOptions={[10, 25, 50, 100]}
- searchBarWQL={{
- suggestions: {
- field(currentValue) {
- return packagesColumns[soPlatform]
- .map(item => ({
- label: item.field,
- description: `filter by ${item.name}`,
- }))
- .sort(sortFieldSuggestion);
- },
- value: async (currentValue, { field }) => {
- try {
- const response = await WzRequest.apiReq(
- 'GET',
- `/syscollector/${agent.id}/packages`,
- {
- params: {
- distinct: true,
- limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
- select: field,
- sort: `+${field}`,
- ...(currentValue
- ? { q: `${field}~${currentValue}` }
- : {}),
- },
- },
- );
- return response?.data?.data.affected_items.map(item => ({
- label: getLodash(item, field),
- }));
- } catch (error) {
- return [];
- }
- },
- },
- }}
- tableProps={{
- tableLayout: 'auto',
- }}
- />
-
+
-
- field)
- .join(',')}`}
- searchTable
- downloadCsv
- showReload
- tablePageSizeOptions={[10, 25, 50, 100]}
- searchBarWQL={{
- suggestions: {
- field(currentValue) {
- return processColumns[soPlatform]
- .map(item => ({
- label: item.field,
- description: `filter by ${item.name}`,
- }))
- .sort(sortFieldSuggestion);
- },
- value: async (currentValue, { field }) => {
- try {
- const response = await WzRequest.apiReq(
- 'GET',
- `/syscollector/${agent.id}/processes`,
- {
- params: {
- distinct: true,
- limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
- select: field,
- sort: `+${field}`,
- ...(currentValue
- ? { q: `${field}~${currentValue}` }
- : {}),
- },
- },
- );
- return response?.data?.data.affected_items.map(item => ({
- label: getLodash(item, field),
- }));
- } catch (error) {
- return [];
- }
- },
- },
- }}
- tableProps={{
- tableLayout: 'auto',
- }}
- />
-
+
diff --git a/plugins/main/public/components/common/hooks/use-app-config.ts b/plugins/main/public/components/common/hooks/use-app-config.ts
index a2a3e93d4b..3e3dae04c9 100644
--- a/plugins/main/public/components/common/hooks/use-app-config.ts
+++ b/plugins/main/public/components/common/hooks/use-app-config.ts
@@ -15,5 +15,10 @@ import { useSelector } from 'react-redux';
export const useAppConfig = () => {
const appConfig = useSelector((state: AppRootState) => state.appConfig);
+ console.log(appConfig, 'appConfig in useAppConfig');
+ console.log(appConfig.data['fim.pattern'], "appConfig.data['fim.pattern']");
+
+ const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern'];
+ console.log(FIM_INDEX_PATTERN_ID, 'FIM_INDEX_PATTERN_ID in useAppConfig');
return appConfig;
-}
+};
diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.js
index 24b3b2b421..4d94450889 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,11 @@ 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 { DashboardFim } from '../../overview/fim/dashboard/overview/dashboard';
+import { InventoryFim } from '../../overview/fim/inventory/inventory';
+
+import { withModuleNotForAgent } from '../hocs';
const DashboardTab = {
id: 'dashboard',
@@ -56,17 +57,41 @@ export const ModulesDefaults = {
fim: {
init: 'dashboard',
tabs: [
- DashboardTab,
+ {
+ id: 'dashboard',
+ name: 'Dashboard',
+ component: DashboardFim,
+ },
{
id: 'inventory',
name: 'Inventory',
- buttons: [ButtonModuleExploreAgent],
- component: MainFim,
+ component: InventoryFim,
},
EventsTab,
],
+ buttons: ['settings'],
availableFor: ['manager', 'agent'],
},
+
+ // vuls: {
+ // init: 'dashboard',
+ // tabs: [
+ // {
+ // id: 'dashboard',
+ // name: 'Dashboard',
+ // component: withModuleNotForAgent(DashboardVuls),
+ // },
+ // {
+ // id: 'inventory',
+ // name: 'Inventory',
+ // component: withModuleNotForAgent(InventoryVuls),
+ // },
+ // EventsTab,
+ // ],
+ // buttons: ['settings'],
+ // availableFor: ['manager'],
+ // },
+
aws: {
init: 'dashboard',
tabs: [DashboardTab, EventsTab],
diff --git a/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx b/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx
index c910eebee8..0b73f1080d 100644
--- a/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx
+++ b/plugins/main/public/components/common/util/agent-group-truncate/group-truncate.tsx
@@ -100,7 +100,7 @@ export class GroupTruncate extends React.Component {
);
}
- renderGroups(groups) {
+ renderGroups(groups = []) {
const { length } = this.props;
let auxGroups: Array
= [];
let tooltipGroups: Array = [];
diff --git a/plugins/main/public/components/common/welcome/agents-info.js b/plugins/main/public/components/common/welcome/agents-info.js
index 44aaa139dc..dd49faecab 100644
--- a/plugins/main/public/components/common/welcome/agents-info.js
+++ b/plugins/main/public/components/common/welcome/agents-info.js
@@ -110,7 +110,8 @@ export class AgentInfo extends Component {
{
- let menuSize;
- if (this.state.isLocked) {
- menuSize = window.innerWidth - this.offset - this.sidebarSizeDefault;
- } else {
- menuSize = window.innerWidth - this.offset;
- }
- let maxModules = 5;
- if (menuSize > 1400) {
- maxModules = 5;
- } else {
- if (menuSize > 1250) {
- maxModules = 4;
- } else {
- if (menuSize > 1100) {
- maxModules = 3;
- } else {
- if (menuSize > 900) {
- maxModules = 2;
- } else {
- maxModules = 1;
- if (menuSize < 750) {
- maxModules = null;
- }
- }
- }
- }
- }
-
- this.setState({ maxModules: maxModules, widthWindow: window.innerWidth });
+ this.setState({ widthWindow: window.innerWidth });
};
/* TODO: we should to create a unique Explore agent button instead
@@ -187,12 +143,15 @@ export const AgentsWelcome = compose(
getDataPlugin().query.filterManager.setFilters(agentFilters);
}
+ clearAgentInUrl() {
+ this.location.search('agent', null);
+ }
+
async componentDidMount() {
this._isMount = true;
/* WORKAROUND: ensure the $scope.agent is synced with the agent stored in Redux (this.props.agent). See agents.js controller.
*/
this.props.setAgent(this.props.agent);
- this.updatePinnedApplications();
this.updateWidth();
const tabVisualizations = new TabVisualizations();
tabVisualizations.removeAll();
@@ -204,12 +163,11 @@ export const AgentsWelcome = compose(
const $injector = getAngularModule().$injector;
this.drawerLokedSubscribtion = getChrome()
.getIsNavDrawerLocked$()
- .subscribe(isLocked => {
- this.setState({ isLocked }, () => {
- this.updateWidth();
- });
+ .subscribe(() => {
+ this.updateWidth();
});
this.router = $injector.get('$route');
+ this.location = $injector.get('$location');
window.addEventListener('resize', this.updateWidth); //eslint-disable-line
await VisFactoryHandler.buildAgentsVisualizations(
filterHandler,
@@ -230,117 +188,34 @@ export const AgentsWelcome = compose(
this.drawerLokedSubscribtion?.unsubscribe();
}
- updatePinnedApplications(applications) {
- let pinnedApplications;
-
- if (applications) {
- pinnedApplications = applications;
- } else {
- pinnedApplications = window.localStorage.getItem(
- 'wz-menu-agent-apps-pinned',
- )
- ? JSON.parse(window.localStorage.getItem('wz-menu-agent-apps-pinned'))
- : [
- // Default pinned applications
- threatHunting.id,
- fileIntegrityMonitoring.id,
- configurationAssessment.id,
- vulnerabilityDetection.id,
- mitreAttack.id,
- ];
- }
-
- // Ensure the pinned applications are supported
- pinnedApplications = pinnedApplications.filter(pinnedApplication =>
- Applications.some(({ id }) => id === pinnedApplication),
- );
-
- window.localStorage.setItem(
- 'wz-menu-agent-apps-pinned',
- JSON.stringify(pinnedApplications),
+ renderEndpointsSummaryButton() {
+ const application = Applications.find(
+ ({ id }) => id === 'endpoints-summary',
);
- this.setState({ menuAgent: pinnedApplications });
- }
-
- renderModules() {
return (
-
- {this.state.menuAgent.map((applicationId, i) => {
- const moduleID = Object.keys(WAZUH_MODULES).find(
- key => WAZUH_MODULES[key]?.appId === applicationId,
- ).appId;
- if (
- i < this.state.maxModules &&
- hasAgentSupportModule(this.props.agent, moduleID)
- ) {
- return (
-
-
-
-
- {
- Applications.find(({ id }) => id === applicationId)
- .title
- }
-
-
-
-
-
- );
- }
- })}
-
-
- this.setState({ switchModule: !this.state.switchModule })
- }
- >
- More...
-
- }
- isOpen={this.state.switchModule}
- closePopover={() => this.setState({ switchModule: false })}
- repositionOnScroll={false}
- anchorPosition='downCenter'
- >
-
-
-
-
- this.updatePinnedApplications(applications)
- }
- closePopover={() => {
- this.setState({ switchModule: false });
- }}
- switchTab={module => this.props.switchTab(module)}
- >
-
-
-
-
-
-
+
+
+ {application.title}
+
+
);
}
renderTitle() {
const notNeedStatus = true;
const thereAreAgentSelected = Boolean(this.props.agent?.id);
+ // Calculate if the header buttons should display the name or only the icon to be responsive
+
return (
- {(this.state.maxModules !== null && this.renderModules()) || (
-
-
- this.setState({
- switchModule: !this.state.switchModule,
- })
- }
- >
- Applications
-
- }
- isOpen={this.state.switchModule}
- closePopover={() => this.setState({ switchModule: false })}
- repositionOnScroll={false}
- anchorPosition='downCenter'
- >
-
-
-
-
- this.updatePinnedApplications(applications)
- }
- closePopover={() => {
- this.setState({ switchModule: false });
- }}
- switchTab={module => this.props.switchTab(module)}
- >
-
-
-
-
-
- )}
+
+ {this.renderEndpointsSummaryButton()}
+
@@ -417,6 +254,7 @@ export const AgentsWelcome = compose(
className='wz-unpin-agent'
iconType='pinFilled'
onClick={() => {
+ this.clearAgentInUrl();
this.props.updateCurrentAgentData({});
this.removeAgentsFilter();
}}
@@ -433,13 +271,14 @@ export const AgentsWelcome = compose(
onClick={() =>
this.props.switchTab('syscollector', notNeedStatus)
}
- tooltip={
- this.state.maxModules === null
- ? { position: 'bottom', content: 'Inventory data' }
- : undefined
- }
+ className='wz-it-hygiene-header-button'
+ tooltip={{
+ position: 'bottom',
+ content: 'Inventory data',
+ className: 'wz-it-hygiene-header-button-tooltip',
+ }}
>
- {this.state.maxModules !== null ? 'Inventory data' : ''}
+ Inventory data
@@ -447,13 +286,14 @@ export const AgentsWelcome = compose(
buttonType='empty'
iconType='stats'
onClick={() => this.props.switchTab('stats', notNeedStatus)}
- tooltip={
- this.state.maxModules === null
- ? { position: 'bottom', content: 'Stats' }
- : undefined
- }
+ className='wz-it-hygiene-header-button'
+ tooltip={{
+ position: 'bottom',
+ content: 'Stats',
+ className: 'wz-it-hygiene-header-button-tooltip',
+ }}
>
- {this.state.maxModules !== null ? 'Stats' : ''}
+ Stats
@@ -463,13 +303,14 @@ export const AgentsWelcome = compose(
onClick={() =>
this.props.switchTab('configuration', notNeedStatus)
}
- tooltip={
- this.state.maxModules === null
- ? { position: 'bottom', content: 'Configuration' }
- : undefined
- }
+ className='wz-it-hygiene-header-button'
+ tooltip={{
+ position: 'bottom',
+ content: 'Configuration',
+ className: 'wz-it-hygiene-header-button-tooltip',
+ }}
>
- {this.state.maxModules !== null ? 'Configuration' : ''}
+ Configuration
diff --git a/plugins/main/public/components/common/welcome/components/agent-sections.ts b/plugins/main/public/components/common/welcome/components/agent-sections.ts
deleted file mode 100644
index 3278e60914..0000000000
--- a/plugins/main/public/components/common/welcome/components/agent-sections.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Wazuh app - Build all sections for MenuAgent.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-
-import { WAZUH_MODULES_ID } from '../../../../../common/constants';
-
-export const getAgentSections = (menuAgent) => {
- return {
- securityInformation: {
- id: 'securityInformation',
- text: 'Security information management',
- isTitle: true,
- },
- auditing: {
- id: 'auditing',
- text: 'Auditing and Policy Monitoring',
- isTitle: true,
- },
- threatDetection: {
- id: 'threatDetection',
- text: 'Threat detection and response',
- isTitle: true,
- },
- regulatoryCompliance: {
- id: 'regulatoryCompliance',
- text: 'Regulatory Compliance',
- isTitle: true,
- },
- general: {
- id: WAZUH_MODULES_ID.SECURITY_EVENTS,
- text: 'Security events',
- isPin: menuAgent.general ? menuAgent.general : false,
- },
- fim: {
- id: WAZUH_MODULES_ID.INTEGRITY_MONITORING,
- text: 'Integrity monitoring',
- isPin: menuAgent.fim ? menuAgent.fim : false,
- },
- aws: {
- id: WAZUH_MODULES_ID.AMAZON_WEB_SERVICES,
- text: 'Amazon AWS',
- isPin: menuAgent.aws ? menuAgent.aws : false,
- },
- gcp: {
- id: WAZUH_MODULES_ID.GOOGLE_CLOUD_PLATFORM,
- text: 'Google Cloud Platform',
- isPin: menuAgent.gcp ? menuAgent.gcp : false,
- },
- github: {
- id: WAZUH_MODULES_ID.GITHUB,
- text: 'GitHub',
- isPin: menuAgent.github ? menuAgent.github : false
- },
- pm: {
- id: WAZUH_MODULES_ID.POLICY_MONITORING,
- text: 'Policy Monitoring',
- isPin: menuAgent.pm ? menuAgent.pm : false,
- },
- sca: {
- id: WAZUH_MODULES_ID.SECURITY_CONFIGURATION_ASSESSMENT,
- text: 'Security configuration assessment',
- isPin: menuAgent.sca ? menuAgent.sca : false,
- },
- audit: {
- id: WAZUH_MODULES_ID.AUDITING,
- text: 'System Auditing',
- isPin: menuAgent.audit ? menuAgent.audit : false,
- },
- oscap: {
- id: WAZUH_MODULES_ID.OPEN_SCAP,
- text: 'OpenSCAP',
- isPin: menuAgent.oscap ? menuAgent.oscap : false,
- },
- ciscat: {
- id: WAZUH_MODULES_ID.CIS_CAT,
- text: 'CIS-CAT',
- isPin: menuAgent.oscap ? menuAgent.oscap : false,
- },
- vuls: {
- id: WAZUH_MODULES_ID.VULNERABILITIES,
- text: 'Vulnerabilities',
- isPin: menuAgent.vuls ? menuAgent.vuls : false,
- },
- virustotal: {
- id: WAZUH_MODULES_ID.VIRUSTOTAL,
- text: 'VirusTotal',
- isPin: menuAgent.virustotal ? menuAgent.virustotal : false,
- },
- osquery: {
- id: WAZUH_MODULES_ID.OSQUERY,
- text: 'Osquery',
- isPin: menuAgent.osquery ? menuAgent.osquery : false,
- },
- docker: {
- id: WAZUH_MODULES_ID.DOCKER,
- text: 'Docker Listener',
- isPin: menuAgent.docker ? menuAgent.docker : false,
- },
- mitre: {
- id: WAZUH_MODULES_ID.MITRE_ATTACK,
- text: 'MITRE ATT&CK',
- isPin: menuAgent.mitre ? menuAgent.mitre : false,
- },
- pci: {
- id: WAZUH_MODULES_ID.PCI_DSS,
- text: 'PCI DSS',
- isPin: menuAgent.pci ? menuAgent.pci : false,
- },
- gdpr: {
- id: WAZUH_MODULES_ID.GDPR,
- text: 'GDPR',
- isPin: menuAgent.gdpr ? menuAgent.gdpr : false,
- },
- hipaa: {
- id: WAZUH_MODULES_ID.HIPAA,
- text: 'HIPAA',
- isPin: menuAgent.hipaa ? menuAgent.hipaa : false,
- },
- nist: {
- id: WAZUH_MODULES_ID.NIST_800_53,
- text: 'NIST 800-53',
- isPin: menuAgent.nist ? menuAgent.nist : false,
- },
- tsc: { id: WAZUH_MODULES_ID.TSC, text: 'TSC', isPin: menuAgent.tsc ? menuAgent.tsc : false },
- };
-};
diff --git a/plugins/main/public/components/common/welcome/components/menu-agent.js b/plugins/main/public/components/common/welcome/components/menu-agent.js
deleted file mode 100644
index f11013ad54..0000000000
--- a/plugins/main/public/components/common/welcome/components/menu-agent.js
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component } from 'react';
-import {
- EuiFlexGrid,
- EuiFlexGroup,
- EuiFlexItem,
- EuiIcon,
- EuiSideNav,
- EuiLink,
-} from '@elastic/eui';
-import { connect } from 'react-redux';
-import { hasAgentSupportModule } from '../../../../react-services/wz-agents';
-import {
- getAngularModule,
- getCore,
- getToasts,
-} from '../../../../kibana-services';
-import { updateCurrentAgentData } from '../../../../redux/actions/appStateActions';
-import { Applications, Categories } from '../../../../utils/applications';
-import { RedirectAppLinks } from '../../../../../../../src/plugins/opensearch_dashboards_react/public';
-
-class WzMenuAgent extends Component {
- constructor(props) {
- super(props);
- this.state = {
- hoverAddFilter: '',
- };
-
- this.appCategories = Applications.reduce((categories, app) => {
- const existingCategory = categories.find(
- category => category.id === app.category,
- );
- if (app.showInAgentMenu) {
- if (existingCategory) {
- existingCategory.apps.push(app);
- } else {
- const category = Categories.find(
- category => app.category === category.id,
- );
- categories.push({
- id: category.id,
- label: Categories.find(category => app.category === category.id)
- .label,
- icon: category.euiIconType,
- apps: [app],
- });
- }
- }
- return categories;
- }, []).sort((a, b) => {
- return (
- Categories.find(category => a.id === category.id).order -
- Categories.find(category => b.id === category.id).order
- );
- });
- }
-
- componentDidMount() {
- const $injector = getAngularModule().$injector;
- this.router = $injector.get('$route');
- }
-
- clickMenuItem = appId => {
- this.props.closePopover();
- // do not redirect if we already are in that tab
- this.props.updateCurrentAgentData(this.props.isAgent);
- this.router.reload();
- };
-
- addToast({ color, title, text, time = 3000 }) {
- getToasts().add({ title, text, toastLifeTimeMs: time, color });
- }
-
- createItems = items => {
- return items
- .filter(item =>
- hasAgentSupportModule(this.props.currentAgentData, item.id),
- )
- .map(item => this.createItem(item));
- };
-
- createItem = (item, data = {}) => {
- // NOTE: Duplicate `name` values will cause `id` collisions.
- return {
- ...data,
- id: item.id,
- name: (
- {
- this.setState({ hoverAddFilter: item.id });
- }}
- onMouseLeave={() => {
- this.setState({ hoverAddFilter: '' });
- }}
- >
- (!item.isTitle ? this.clickMenuItem(item.id) : null)}
- style={{ cursor: !item.isTitle ? 'pointer' : 'normal' }}
- >
-
-
- {item.title}
-
-
-
- {this.state.hoverAddFilter === item.id &&
- !item.isTitle &&
- (this.props.pinnedApplications.length < 6 || item.isPin) &&
- (this.props.pinnedApplications.length > 1 || !item.isPin) && (
-
- {
- if (
- !item.isPin &&
- this.props.pinnedApplications.length < 6
- ) {
- this.props.updatePinnedApplications([
- ...this.props.pinnedApplications,
- item.id,
- ]);
- } else if (
- this.props.pinnedApplications.includes(item.id)
- ) {
- this.props.updatePinnedApplications([
- ...this.props.pinnedApplications.filter(
- id => id !== item.id,
- ),
- ]);
- } else {
- this.addToast({
- title:
- 'The limit of pinned applications has been reached',
- color: 'danger',
- });
- }
- }}
- color='primary'
- type={
- this.props.pinnedApplications.includes(item.id)
- ? 'pinFilled'
- : 'pin'
- }
- aria-label='Next'
- style={{ cursor: 'pointer' }}
- />
-
- )}
-
- ),
- isSelected: this.props.currentTab === item.id,
- };
- };
-
- render() {
- const items = this.appCategories.map(({ apps, ...rest }) => ({
- ...rest,
- items: this.createItems(
- apps.map(app => ({
- id: app.id,
- title: app.title,
- isPin: this.props.pinnedApplications.includes(app.id),
- })),
- ),
- }));
-
- return (
-
-
-
- {items.map(item => (
-
- ,
- items: item.items,
- },
- ]}
- style={{ padding: '4px 12px' }}
- />
-
- ))}
-
-
-
- );
- }
-}
-
-const mapStateToProps = state => {
- return {
- currentAgentData: state.appStateReducers.currentAgentData,
- currentTab: state.appStateReducers.currentTab,
- };
-};
-
-const mapDispatchToProps = dispatch => ({
- updateCurrentAgentData: agentData =>
- dispatch(updateCurrentAgentData(agentData)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(WzMenuAgent);
diff --git a/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx b/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx
index 9814642042..39224fde2c 100644
--- a/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx
+++ b/plugins/main/public/components/common/welcome/components/sca_scan/sca_scan.tsx
@@ -90,6 +90,12 @@ export const ScaScan = compose(
this.getLastScan(this.props.agent.id);
}
+ async componentDidUpdate(prevProps: Readonly) {
+ if (prevProps.agent.id !== this.props.agent.id) {
+ this.getLastScan(this.props.agent.id);
+ }
+ }
+
async getLastScan(agentId: Number) {
const scans = await WzRequest.apiReq(
'GET',
@@ -209,7 +215,7 @@ export const ScaScan = compose(
href={getCore().application.getUrlForApp(
configurationAssessment.id,
{
- path: `#/overview?tab=sca&redirectPolicy=${lastScan.policy_id}`,
+ path: `#/overview?tab=sca&redirectPolicy=${lastScan?.policy_id}`,
},
)}
>
@@ -220,7 +226,7 @@ export const ScaScan = compose(
- {lastScan.policy_id}
+ {lastScan?.policy_id}
@@ -258,6 +264,20 @@ export const ScaScan = compose(
const loading = this.renderLoadingStatus();
const scaScan = this.renderScanDetails();
const emptyPrompt = this.renderEmptyPrompt();
+ if (loading) {
+ return (
+
+ {loading}
+
+ );
+ }
+ if (!lastScan) {
+ return (
+
+ {emptyPrompt}
+
+ );
+ }
return (
@@ -277,7 +297,7 @@ export const ScaScan = compose(
href={getCore().application.getUrlForApp(
configurationAssessment.id,
{
- path: `#/overview?tab=sca&redirectPolicy=${lastScan.policy_id}`,
+ path: `#/overview?tab=sca&redirectPolicy=${lastScan?.policy_id}`,
},
)}
>
@@ -309,8 +329,6 @@ export const ScaScan = compose(
- {lastScan === undefined && emptyPrompt}
- {loading}
{scaScan}
diff --git a/plugins/main/public/components/common/welcome/welcome.scss b/plugins/main/public/components/common/welcome/welcome.scss
index e04bf5525c..dab373eee2 100644
--- a/plugins/main/public/components/common/welcome/welcome.scss
+++ b/plugins/main/public/components/common/welcome/welcome.scss
@@ -1,44 +1,84 @@
-.wz-welcome-page .euiCard .euiTitle, .wz-module-body .euiCard .euiTitle {
- font-size: 16px;
- font-weight: 400;
+.wz-welcome-page .euiCard .euiTitle,
+.wz-module-body .euiCard .euiTitle {
+ font-size: 16px;
+ font-weight: 400;
}
-.wz-welcome-page .euiCard .euiText, .wz-module-body .euiCard .euiText {
- font-size: 12px;
- font-family: sans-serif;
+.wz-welcome-page .euiCard .euiText,
+.wz-module-body .euiCard .euiText {
+ font-size: 12px;
+ font-family: sans-serif;
}
-.wz-module-header-agent:not(.wz-module-header-agent-main){
- background: white;
- border-bottom: 1px solid #D3DAE6;
+.wz-module-header-agent:not(.wz-module-header-agent-main) {
+ background: white;
+ border-bottom: 1px solid #d3dae6;
}
-.wz-welcome-page-agent-info.wz-welcome-page-agent-info-gray{
- border-top: 1px solid #d3dae6;
- background: #fafbfd!important;
- border-bottom: 1px solid #d3dae6;
+.wz-welcome-page-agent-info.wz-welcome-page-agent-info-gray {
+ border-top: 1px solid #d3dae6;
+ background: #fafbfd !important;
+ border-bottom: 1px solid #d3dae6;
}
-.wz-welcome-page-agent-tabs{
- padding: 12px 16px 1px 10px;
- min-height: 54px;
- border-bottom: 1px solid #D3DAE6;
- background-color: white;
+.wz-welcome-page-agent-tabs {
+ padding: 12px 16px 1px 10px;
+ min-height: 54px;
+ border-bottom: 1px solid #d3dae6;
+ background-color: white;
}
-.wz-welcome-page-agent-info-actions{
- padding: 6px 0px 6px 0px;
+.wz-welcome-page-agent-info-actions {
+ padding: 6px 0px 6px 0px;
}
-.wz-welcome-page-agent-info .euiStat .euiText{
- font-size: 12px;
- font-family: sans-serif;
+.wz-welcome-page-agent-info .euiStat .euiText {
+ font-size: 12px;
+ font-family: sans-serif;
}
.statWithLink:hover .euiTitle {
- text-decoration: underline;
+ text-decoration: underline;
}
-span.statWithLink:hover {
- text-decoration: underline;
-}
\ No newline at end of file
+span.statWithLink:hover {
+ text-decoration: underline;
+}
+
+// Header buttons of IT Hygiene application
+
+// Sidebar is open and locked
+body.euiBody--hasFlyout:not(.euiBody-hasOverlayMask) {
+ @media only screen and (max-width: 1345px) {
+ // Hide button text depending on the window size
+ .wz-it-hygiene-header-button .euiButtonEmpty__text {
+ display: none;
+ }
+ }
+
+ @media only screen and (min-width: 1346px) {
+ // Hide the tooltip of button depending on the window size
+ .wz-it-hygiene-header-button-tooltip {
+ display: none;
+ }
+ }
+}
+
+// Sidebar is closed
+body:not(.euiBody--hasFlyout) {
+ @media only screen and (max-width: 1025px) {
+ // Hide button text depending on the window size
+ .wz-it-hygiene-header-button .euiButtonEmpty__text {
+ display: none;
+ }
+ }
+
+ @media only screen and (min-width: 1026px) {
+ // Hide the tooltip of button depending on the window size
+ .wz-it-hygiene-header-button-tooltip {
+ display: none;
+ }
+ }
+}
+
+// Header buttons of IT Hygiene application
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/health-check/services/check-pattern-support.service.ts b/plugins/main/public/components/health-check/services/check-pattern-support.service.ts
index 611f6fd1a2..aca4d986a4 100644
--- a/plugins/main/public/components/health-check/services/check-pattern-support.service.ts
+++ b/plugins/main/public/components/health-check/services/check-pattern-support.service.ts
@@ -12,6 +12,7 @@
*
*/
import { SavedObject } from '../../../react-services';
+import { WarningError } from '../../../react-services/error-management/error-factory/errors/WarningError';
import { CheckLogger } from '../types/check_logger';
export const checkPatternSupportService =
@@ -29,52 +30,50 @@ export const checkPatternSupportService =
`Getting indices fields for the index pattern id [${pattern}]...`,
);
const fields = await SavedObject.getIndicesFields(pattern, indexType);
- if (fields) {
- checkLogger.info(
- `Fields for index pattern id [${pattern}] found: ${fields.length}`,
- );
- checkLogger.info(`Creating saved object for the index pattern with id [${pattern}].
+ checkLogger.info(
+ `Fields for index pattern id [${pattern}] found: ${fields.length}`,
+ );
+ checkLogger.info(`Creating saved object for the index pattern with id [${pattern}].
title: ${pattern}
id: ${pattern}
timeFieldName: ${timeFieldName}
${fields ? `fields: ${fields.length}` : ''}`);
- await SavedObject.createSavedObject(
- 'index-pattern',
- pattern,
- {
- attributes: {
- title: pattern,
- timeFieldName,
- },
+ await SavedObject.createSavedObject(
+ 'index-pattern',
+ pattern,
+ {
+ attributes: {
+ title: pattern,
+ timeFieldName,
},
- fields,
- );
- checkLogger.action(
- `Created the saved object for the index pattern id [${pattern}]`,
- );
- const indexPatternSavedObjectIDs = [pattern];
- // Check the index pattern saved objects can be found using `GET /api/saved_objects/_find` endpoint.
- // Related issue: https://github.com/wazuh/wazuh-dashboard-plugins/issues/4293
- checkLogger.info(
- `Checking the integrity of saved objects. Validating ${indexPatternSavedObjectIDs.join(
- ',',
- )} can be found...`,
- );
- await SavedObject.validateIndexPatternSavedObjectCanBeFound(
- indexPatternSavedObjectIDs,
- );
- checkLogger.info('Integrity of saved objects: [ok]');
+ },
+ fields,
+ );
+ checkLogger.action(
+ `Created the saved object for the index pattern id [${pattern}]`,
+ );
+ const indexPatternSavedObjectIDs = [pattern];
+ // Check the index pattern saved objects can be found using `GET /api/saved_objects/_find` endpoint.
+ // Related issue: https://github.com/wazuh/wazuh-dashboard-plugins/issues/4293
+ checkLogger.info(
+ `Checking the integrity of saved objects. Validating ${indexPatternSavedObjectIDs.join(
+ ',',
+ )} can be found...`,
+ );
+ await SavedObject.validateIndexPatternSavedObjectCanBeFound(
+ indexPatternSavedObjectIDs,
+ );
+ checkLogger.info('Integrity of saved objects: [ok]');
+ } catch (error) {
+ if (error.name === 'WarningError') {
+ checkLogger.warning(error.message || error);
} else {
- checkLogger.warning(
- `No indices fields found for index pattern id [${pattern}], it is necessary to check the index...`,
+ checkLogger.error(
+ `Error creating index pattern id [${pattern}]: ${
+ error.message || error
+ }`,
);
}
- } catch (error) {
- checkLogger.error(
- `Error creating index pattern id [${pattern}]: ${
- error.message || error
- }`,
- );
}
}
};
diff --git a/plugins/main/public/components/overview/fim/dashboard/index.ts b/plugins/main/public/components/overview/fim/dashboard/index.ts
new file mode 100644
index 0000000000..ee1046d206
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/index.ts
@@ -0,0 +1 @@
+export * from './overview';
diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/dashboard.tsx b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard.tsx
new file mode 100644
index 0000000000..09a779564e
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard.tsx
@@ -0,0 +1,153 @@
+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 useSearchBarConfiguration from '../../../vulnerabilities/search_bar/use_search_bar_configuration';
+import { getDashboardFilters } from './dashboard_panels_filters';
+import './fim_filters.scss';
+import { getKPIsPanel } from './dashboard_panels_kpis';
+import { useAppConfig } from '../../../../common/hooks';
+import { withErrorBoundary } from '../../../../common/hocs';
+import { DiscoverNoResults } from '../../../vulnerabilities/common/components/no_results';
+import { WAZUH_INDEX_TYPE_FIM } from '../../../../../../common/constants';
+import { LoadingSpinner } from '../../../vulnerabilities/common/components/loading_spinner';
+import useCheckIndexFields from '../../../vulnerabilities/common/hooks/useCheckIndexFields';
+
+const plugins = getPlugins();
+
+const SearchBar = getPlugins().data.ui.SearchBar;
+
+const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer;
+
+const DashboardFimComponent: React.FC = () => {
+ console.log('El componente está montándose...');
+
+ const appConfig = useAppConfig();
+
+ const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern'];
+ console.log(FIM_INDEX_PATTERN_ID, 'FIM_INDEX_PATTERN_ID');
+ console.log(
+ FIM_INDEX_PATTERN_ID,
+ 'FIM_INDEX_PATTERN_ID antes de useSearchBarConfiguration',
+ );
+
+ const { searchBarProps } = useSearchBarConfiguration({
+ defaultIndexPatternID: FIM_INDEX_PATTERN_ID,
+ });
+ const {
+ isLoading: isLoadingSearchbar,
+ filters,
+ query,
+ indexPatterns,
+ } = searchBarProps;
+
+ const { isError, error, isSuccess, resultIndexData, isLoading } =
+ useCheckIndexFields(
+ FIM_INDEX_PATTERN_ID,
+ indexPatterns?.[0],
+ WAZUH_INDEX_TYPE_FIM,
+ filters,
+ query,
+ );
+ console.log(searchBarProps, 'searchBarProps');
+ console.log(indexPatterns, 'indexPatterns');
+
+ return (
+ <>
+
+ <>
+ {isLoading || isLoadingSearchbar ? : null}
+ {!isLoading && !isLoadingSearchbar ? (
+
+ ) : null}
+ {!isLoadingSearchbar &&
+ !isLoading &&
+ (isError || resultIndexData?.hits?.total === 0) ? (
+
+ ) : null}
+ {!isLoadingSearchbar && !isLoading && isSuccess ? (
+ <>
+
+
+
+
+
+ >
+ ) : null}
+ >
+
+ >
+ );
+};
+
+export const DashboardFim = withErrorBoundary(DashboardFimComponent);
diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels.ts b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels.ts
new file mode 100644
index 0000000000..e9fdb21227
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels.ts
@@ -0,0 +1,632 @@
+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',
+ },
+ ],
+ },
+ };
+};
+
+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',
+ },
+ ],
+ },
+ };
+};
+
+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: '@timestamp',
+ 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: '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/overview/dashboard_panels_filters.ts b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_filters.ts
new file mode 100644
index 0000000000..50e85411b4
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/overview/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.full',
+ ),
+ },
+ },
+ 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 fim',
+ 'Vulnerability',
+ 'vulnerability.id',
+ ),
+ },
+ },
+ };
+};
diff --git a/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_kpis.ts
new file mode 100644
index 0000000000..f36425473a
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/overview/dashboard_panels_kpis.ts
@@ -0,0 +1,414 @@
+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: {},
+ schema: 'metric',
+ },
+ {
+ id: '2',
+ enabled: true,
+ type: 'filters',
+ params: {
+ filters: [
+ {
+ input: {
+ query: 'data.vulnerability.severity: "Critical"',
+ language: 'kuery',
+ },
+ label: '',
+ },
+ ],
+ },
+ 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/overview/fim_filters.scss b/plugins/main/public/components/overview/fim/dashboard/overview/fim_filters.scss
new file mode 100644
index 0000000000..a836b86e3e
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/overview/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/overview/index.tsx b/plugins/main/public/components/overview/fim/dashboard/overview/index.tsx
new file mode 100644
index 0000000000..b691822976
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/dashboard/overview/index.tsx
@@ -0,0 +1 @@
+export * from './dashboard';
\ 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..c1ecc93fe1
--- /dev/null
+++ b/plugins/main/public/components/overview/fim/inventory/inventory.tsx
@@ -0,0 +1,265 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { getPlugins } from '../../../../kibana-services';
+import useSearchBarConfiguration from '../../fim/search_bar/use_search_bar_configuration';
+
+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 '../../fim/doc_viewer/doc_viewer';
+import { DiscoverNoResults } from '../../fim/common/components/no_results';
+import { LoadingSpinner } from '../../fim/common/components/loading_spinner';
+import { useDataGrid } from '../../fim/data_grid/use_data_grid';
+import {
+ MAX_ENTRIES_PER_QUERY,
+ inventoryTableDefaultColumns,
+} from '../../fim/dashboards/inventory/config';
+import { useDocViewer } from '../../fim/doc_viewer/use_doc_viewer';
+import './inventory.scss';
+import {
+ search,
+ exportSearchToCSV,
+} from '../../fim/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';
+import { WAZUH_INDEX_TYPE_FIM } from '../../../../../common/constants';
+import useCheckIndexFields from '../../fim/common/hooks/useCheckIndexFields';
+
+const InventoryFimComponent = () => {
+ const appConfig = useAppConfig();
+ const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern'];
+ const { searchBarProps } = useSearchBarConfiguration({
+ 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,
+ });
+
+ const {
+ isError,
+ error,
+ isSuccess,
+ resultIndexData,
+ isLoading: isLoadingCheckIndex,
+ } = useCheckIndexFields(
+ FIM_INDEX_PATTERN_ID,
+ indexPatterns?.[0],
+ WAZUH_INDEX_TYPE_FIM,
+ filters,
+ query,
+ );
+
+ useEffect(() => {
+ if (!isLoading && isSuccess) {
+ 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),
+ isLoadingCheckIndex,
+ ]);
+
+ 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 || isLoadingCheckIndex ? (
+
+ ) : (
+
+ )}
+ {isSearching ? : null}
+ {!isLoading &&
+ !isSearching &&
+ (isError ||
+ results?.hits?.total === 0 ||
+ resultIndexData?.hits?.total === 0) ? (
+
+ ) : null}
+ {!isLoading &&
+ !isSearching &&
+ isSuccess &&
+ 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/vulnerabilities/common/components/no_results.tsx b/plugins/main/public/components/overview/vulnerabilities/common/components/no_results.tsx
index 3d592c867d..babfd51d32 100644
--- a/plugins/main/public/components/overview/vulnerabilities/common/components/no_results.tsx
+++ b/plugins/main/public/components/overview/vulnerabilities/common/components/no_results.tsx
@@ -28,170 +28,32 @@
* under the License.
*/
-import React, { Fragment } from 'react';
+import React from 'react';
import { FormattedMessage, I18nProvider } from '@osd/i18n/react';
-import {
- EuiCallOut,
- EuiCode,
- EuiDescriptionList,
- EuiLink,
- EuiPanel,
- EuiSpacer,
- EuiText,
-} from '@elastic/eui';
+import { EuiCallOut, EuiPanel } from '@elastic/eui';
interface Props {
- timeFieldName?: string;
- queryLanguage?: string;
+ message?: string;
}
-export const DiscoverNoResults = ({ timeFieldName, queryLanguage }: Props) => {
- let timeFieldMessage;
-
- if (timeFieldName) {
- timeFieldMessage = (
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-
- let luceneQueryMessage;
-
- if (queryLanguage === 'lucene') {
- const searchExamples = [
- {
- description: 200,
- title: (
-
-
-
-
-
- ),
- },
- {
- description: status:200,
- title: (
-
-
-
-
-
- ),
- },
- {
- description: status:[400 TO 499],
- title: (
-
-
-
-
-
- ),
- },
- {
- description: status:[400 TO 499] AND extension:PHP,
- title: (
-
-
-
-
-
- ),
- },
- {
- description: status:[400 TO 499] AND (extension:php OR extension:html),
- title: (
-
-
-
-
-
- ),
- },
- ];
-
- luceneQueryMessage = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-
+export const DiscoverNoResults = ({ message }: Props) => {
return (
-
+
+ message ?? (
+
+ )
}
- color="warning"
- iconType="help"
- data-test-subj="discoverNoResults"
+ color='warning'
+ iconType='help'
+ data-test-subj='discoverNoResults'
/>
- {timeFieldMessage}
- {luceneQueryMessage}
);
diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx
new file mode 100644
index 0000000000..75112cbc85
--- /dev/null
+++ b/plugins/main/public/components/overview/vulnerabilities/common/hooks/useCheckIndexFields.tsx
@@ -0,0 +1,83 @@
+import { useState, useEffect } from 'react';
+import { search } from '../../dashboards/inventory/inventory_service';
+import {
+ IIndexPattern,
+ IndexPattern,
+ Filter,
+} from '../../../../../../../../src/plugins/data/public';
+import {
+ ErrorFactory,
+ HttpError,
+} from '../../../../../react-services/error-management';
+import { SavedObject } from '../../../../../react-services';
+
+interface UseCheckIndexFieldsResult {
+ isLoading: boolean;
+ isSuccess: boolean;
+ isError: boolean;
+ error: Error | null;
+ resultIndexData: any;
+}
+
+const useCheckIndexFields = (
+ indexPatternId: string,
+ indexPattern: IIndexPattern | undefined,
+ indexType: string,
+ filters?: Filter[],
+ query?: any,
+) => {
+ const [isError, setIsError] = useState(false);
+ const [error, setError] = useState(null);
+ const [isSuccess, setIsSuccess] = useState(false);
+ const [resultIndexData, setResultIndexData] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ if (indexPatternId) {
+ const checkIndexFields = async () => {
+ try {
+ // Check that the index exists
+ await SavedObject.getIndicesFields(indexPatternId, indexType);
+ setIsSuccess(true);
+
+ // Check that the index has data
+ search({
+ indexPattern: indexPattern as IndexPattern,
+ filters,
+ query,
+ })
+ .then((results: any) => {
+ setResultIndexData(results);
+ setIsLoading(false);
+ })
+ .catch((error: any) => {
+ const searchError = ErrorFactory.create(HttpError, {
+ error,
+ message: 'Error fetching vulnerabilities',
+ });
+ setError(searchError);
+ setIsError(true);
+ setIsLoading(false);
+ });
+ } catch (error) {
+ setError(error);
+ setIsError(true);
+ setIsSuccess(false);
+ setIsLoading(false);
+ }
+ };
+
+ checkIndexFields();
+ }
+ }, [indexPatternId]);
+
+ return {
+ isError,
+ error,
+ isSuccess,
+ resultIndexData,
+ isLoading,
+ } as UseCheckIndexFieldsResult;
+};
+
+export default useCheckIndexFields;
diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts
index f6d6c14a0d..c6e98c1bb0 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/config/index.ts
@@ -9,9 +9,6 @@ export const inventoryTableDefaultColumns: EuiDataGridColumn[] = [
{
id: 'package.version',
},
- {
- id: 'package.architecture',
- },
{
id: 'vulnerability.severity',
},
@@ -24,7 +21,4 @@ export const inventoryTableDefaultColumns: EuiDataGridColumn[] = [
{
id: 'vulnerability.score.base',
},
- {
- id: 'event.created',
- }
]
\ No newline at end of file
diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx
index 4f7cdc1518..121b356289 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx
@@ -15,8 +15,6 @@ import {
EuiFlyoutHeader,
EuiTitle,
EuiButtonEmpty,
- EuiCallOut,
- EuiSpacer,
} from '@elastic/eui';
import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
import { SearchResponse } from '../../../../../../../../src/core/server';
@@ -37,6 +35,8 @@ 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';
+import { WAZUH_INDEX_TYPE_VULNERABILITIES } from '../../../../../../common/constants';
+import useCheckIndexFields from '../../common/hooks/useCheckIndexFields';
const InventoryVulsComponent = () => {
const appConfig = useAppConfig();
@@ -93,8 +93,22 @@ const InventoryVulsComponent = () => {
indexPattern: indexPattern as IndexPattern,
});
+ const {
+ isError,
+ error,
+ isSuccess,
+ resultIndexData,
+ isLoading: isLoadingCheckIndex,
+ } = useCheckIndexFields(
+ VULNERABILITIES_INDEX_PATTERN_ID,
+ indexPatterns?.[0],
+ WAZUH_INDEX_TYPE_VULNERABILITIES,
+ filters,
+ query,
+ );
+
useEffect(() => {
- if (!isLoading) {
+ if (!isLoading && isSuccess) {
setIndexPattern(indexPatterns?.[0] as IndexPattern);
search({
indexPattern: indexPatterns?.[0] as IndexPattern,
@@ -120,12 +134,9 @@ const InventoryVulsComponent = () => {
JSON.stringify(searchBarProps),
JSON.stringify(pagination),
JSON.stringify(sorting),
+ isLoadingCheckIndex,
]);
- const timeField = indexPattern?.timeFieldName
- ? indexPattern.timeFieldName
- : undefined;
-
const onClickExportResults = async () => {
const params = {
indexPattern: indexPatterns?.[0] as IndexPattern,
@@ -161,7 +172,7 @@ const InventoryVulsComponent = () => {
grow
>
<>
- {isLoading ? (
+ {isLoading || isLoadingCheckIndex ? (
) : (
{
/>
)}
{isSearching ? : null}
- {!isLoading && !isSearching && results?.hits?.total === 0 ? (
-
+ {!isLoading &&
+ !isSearching &&
+ (isError ||
+ results?.hits?.total === 0 ||
+ resultIndexData?.hits?.total === 0) ? (
+
) : null}
- {!isLoading && !isSearching && results?.hits?.total > 0 ? (
+ {!isLoading &&
+ !isSearching &&
+ isSuccess &&
+ results?.hits?.total > 0 ? (
}
-export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => {
+export const getFieldValueFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => {
const field = indexPattern.fields.find((field) => field.name === columnId);
let fieldValue = null;
if (columnId.includes('.')) {
@@ -93,13 +93,19 @@ export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed)
fieldValue = fieldValue[field];
}
});
-
} else {
- fieldValue = rowsParsed[rowIndex][columnId].formatted
- ? rowsParsed[rowIndex][columnId].formatted
- : rowsParsed[rowIndex][columnId];
+ const rowValue = rowsParsed[rowIndex];
+ // when not exist the column in the row value then the value is null
+ if(!rowValue.hasOwnProperty(columnId)){
+ fieldValue = null;
+ }else{
+ fieldValue = rowValue[columnId]?.formatted || rowValue[columnId];
+ }
+ }
+ // when fieldValue is null or undefined then return a empty string
+ if (fieldValue === null || fieldValue === undefined) {
+ return '';
}
-
// if is date field
if (field?.type === 'date') {
// @ts-ignore
diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx
index 5328214a06..911548c996 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard.tsx
@@ -8,6 +8,11 @@ import { getDashboardFilters } from './dashboard_panels_filters';
import './vulnerability_detector_filters.scss';
import { getKPIsPanel } from './dashboard_panels_kpis';
import { useAppConfig } from '../../../../common/hooks';
+import { withErrorBoundary } from '../../../../common/hocs';
+import { DiscoverNoResults } from '../../common/components/no_results';
+import { WAZUH_INDEX_TYPE_VULNERABILITIES } from '../../../../../../common/constants';
+import { LoadingSpinner } from '../../common/components/loading_spinner';
+import useCheckIndexFields from '../../common/hooks/useCheckIndexFields';
const plugins = getPlugins();
const SearchBar = getPlugins().data.ui.SearchBar;
@@ -18,95 +23,127 @@ const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer;
a wrapper for visual adjustments, while the Kpi, the Open vs Close visualization and
the rest of the visualizations have different configurations at the dashboard level. */
-export const DashboardVuls: React.FC = () => {
+const DashboardVulsComponent: React.FC = () => {
const appConfig = useAppConfig();
const VULNERABILITIES_INDEX_PATTERN_ID =
appConfig.data['vulnerabilities.pattern'];
-
const { searchBarProps } = useSearchBarConfiguration({
defaultIndexPatternID: VULNERABILITIES_INDEX_PATTERN_ID,
- filters: [],
});
+ const {
+ isLoading: isLoadingSearchbar,
+ filters,
+ query,
+ indexPatterns,
+ } = searchBarProps;
+
+ const { isError, error, isSuccess, resultIndexData, isLoading } =
+ useCheckIndexFields(
+ VULNERABILITIES_INDEX_PATTERN_ID,
+ indexPatterns?.[0],
+ WAZUH_INDEX_TYPE_VULNERABILITIES,
+ filters,
+ query,
+ );
return (
<>
-
+ <>
+ {isLoading || isLoadingSearchbar ? : null}
+ {!isLoading && !isLoadingSearchbar ? (
+
+ ) : null}
+ {!isLoadingSearchbar &&
+ !isLoading &&
+ (isError || resultIndexData?.hits?.total === 0) ? (
+
+ ) : null}
+ {!isLoadingSearchbar && !isLoading && isSuccess ? (
+ <>
+
+
+
+
+
+ >
+ ) : null}
+ >
-
-
-
-
-
>
);
};
+
+export const DashboardVuls = withErrorBoundary(DashboardVulsComponent);
diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts
index 76aa50fa3b..d4d44a3f23 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels.ts
@@ -123,22 +123,6 @@ const getVisStateTopVulnerabilities = (indexPatternId: string) => {
},
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',
- },
],
},
};
@@ -273,23 +257,6 @@ const getVisStateTopVulnerabilitiesEndpoints = (indexPatternId: string) => {
},
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',
- },
],
},
};
@@ -431,7 +398,7 @@ const getVisStateAccumulationMostDetectedVulnerabilities = (
enabled: true,
type: 'date_histogram',
params: {
- field: 'event.created',
+ field: '@timestamp',
timeRange: {
from: 'now-24h',
to: 'now',
@@ -526,23 +493,6 @@ const getVisStateInventoryTable = (indexPatternId: string) => {
},
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,
diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts
index 387d5d8f4f..791b505bb1 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_filters.ts
@@ -112,7 +112,7 @@ export const getDashboardFilters = (
indexPatternId,
'Top Operating system vulnerabilities',
'Operating system',
- 'host.os.name',
+ 'host.os.full',
),
},
},
diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts
index e86749e9ec..a4494d9c05 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/overview/dashboard_panels_kpis.ts
@@ -72,7 +72,7 @@ const getVisStateSeverityCritical = (indexPatternId: string) => {
filters: [
{
input: {
- query: 'vulnerability.severity:"critical"',
+ query: 'vulnerability.severity:"Critical"',
language: 'kuery',
},
label: '- Critical Severity Alerts',
@@ -164,7 +164,7 @@ const getVisStateSeverityHigh = (indexPatternId: string) => {
filters: [
{
input: {
- query: 'vulnerability.severity:"high"',
+ query: 'vulnerability.severity:"High"',
language: 'kuery',
},
label: '- High Severity Alerts',
@@ -249,7 +249,7 @@ const getVisStateSeverityMedium = (indexPatternId: string) => {
filters: [
{
input: {
- query: 'vulnerability.severity:"medium"',
+ query: 'vulnerability.severity:"Medium"',
language: 'kuery',
},
label: '- Medium Severity Alerts',
@@ -334,7 +334,7 @@ const getVisStateSeverityLow = (indexPatternId: string) => {
filters: [
{
input: {
- query: 'vulnerability.severity:"low"',
+ query: 'vulnerability.severity:"Low"',
language: 'kuery',
},
label: '- Low Severity Alerts',
diff --git a/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts b/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts
index a43810fdf9..86dacd8b5d 100644
--- a/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts
+++ b/plugins/main/public/components/overview/vulnerabilities/data_grid/use_data_grid.ts
@@ -2,7 +2,7 @@ import { EuiDataGridCellValueElementProps, EuiDataGridColumn, EuiDataGridProps,
import { useEffect, useMemo, useState, Fragment } from "react";
import { SearchResponse } from "@opensearch-project/opensearch/api/types";
import { IFieldType, IndexPattern } from "../../../../../../../src/plugins/data/common";
-import { parseData, getFieldFormatted } from '../dashboards/inventory/inventory_service';
+import { parseData, getFieldValueFormatted } from '../dashboards/inventory/inventory_service';
import { MAX_ENTRIES_PER_QUERY } from "../dashboards/inventory/config";
type tDataGridProps = {
@@ -72,7 +72,7 @@ export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => {
// then the rowIndex is relative to the current page
const relativeRowIndex = rowIndex % pagination.pageSize;
return rowsParsed.hasOwnProperty(relativeRowIndex)
- ? getFieldFormatted(relativeRowIndex, columnId, indexPattern, rowsParsed)
+ ? getFieldValueFormatted(relativeRowIndex, columnId, indexPattern, rowsParsed)
: null;
};
diff --git a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts
index 37685811d8..8ec10959b9 100644
--- a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts
+++ b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.test.ts
@@ -1,7 +1,10 @@
import { renderHook } from '@testing-library/react-hooks';
import '@testing-library/jest-dom/extend-expect';
// osd dependencies
-import { Start, dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
+import {
+ Start,
+ dataPluginMock,
+} from '../../../../../../../src/plugins/data/public/mocks';
import {
Filter,
IndexPattern,
@@ -41,7 +44,7 @@ mockedGetDataPlugin.mockImplementation(
},
},
},
- } as Start)
+ } as Start),
);
///////////////////////////////////////////////////////////
@@ -77,7 +80,9 @@ describe('[hook] useSearchBarConfiguration', () => {
jest
.spyOn(mockDataPlugin.indexPatterns, 'getDefault')
.mockResolvedValue(mockedDefaultIndexPatternData);
- jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]);
+ jest
+ .spyOn(mockDataPlugin.query.filterManager, 'getFilters')
+ .mockReturnValue([]);
const { result, waitForNextUpdate } = renderHook(() => useSearchBar({}));
await waitForNextUpdate();
expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled();
@@ -93,15 +98,21 @@ describe('[hook] useSearchBarConfiguration', () => {
id: exampleIndexPatternId,
title: '',
};
- jest.spyOn(mockDataPlugin.indexPatterns, 'get').mockResolvedValue(mockedIndexPatternData);
+ jest
+ .spyOn(mockDataPlugin.indexPatterns, 'get')
+ .mockResolvedValue(mockedIndexPatternData);
const { result, waitForNextUpdate } = renderHook(() =>
useSearchBar({
defaultIndexPatternID: 'wazuh-index-pattern',
- })
+ }),
);
await waitForNextUpdate();
- expect(mockDataPlugin.indexPatterns.get).toBeCalledWith(exampleIndexPatternId);
- expect(result.current.searchBarProps.indexPatterns).toMatchObject([mockedIndexPatternData]);
+ expect(mockDataPlugin.indexPatterns.get).toBeCalledWith(
+ exampleIndexPatternId,
+ );
+ expect(result.current.searchBarProps.indexPatterns).toMatchObject([
+ mockedIndexPatternData,
+ ]);
});
it('should show an ERROR message and get the default app index pattern when not found the index pattern data by the ID received', async () => {
@@ -112,19 +123,25 @@ describe('[hook] useSearchBarConfiguration', () => {
jest
.spyOn(mockDataPlugin.indexPatterns, 'getDefault')
.mockResolvedValue(mockedDefaultIndexPatternData);
- jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]);
+ jest
+ .spyOn(mockDataPlugin.query.filterManager, 'getFilters')
+ .mockReturnValue([]);
// mocking console error to avoid logs in test and check if is called
- const mockedConsoleError = jest.spyOn(console, 'error').mockImplementationOnce(() => {});
+ const mockedConsoleError = jest
+ .spyOn(console, 'error')
+ .mockImplementationOnce(() => {});
const { result, waitForNextUpdate } = renderHook(() =>
useSearchBar({
defaultIndexPatternID: 'invalid-index-pattern-id',
- })
+ }),
);
await waitForNextUpdate();
expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled();
- expect(mockDataPlugin.indexPatterns.get).toBeCalledWith('invalid-index-pattern-id');
+ expect(mockDataPlugin.indexPatterns.get).toBeCalledWith(
+ 'invalid-index-pattern-id',
+ );
expect(result.current.searchBarProps.indexPatterns).toMatchObject([
mockedDefaultIndexPatternData,
]);
@@ -145,50 +162,22 @@ describe('[hook] useSearchBarConfiguration', () => {
jest
.spyOn(mockDataPlugin.indexPatterns, 'getDefault')
.mockResolvedValue(mockedDefaultIndexPatternData);
- jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue(defaultFilters);
+ jest
+ .spyOn(mockDataPlugin.query.filterManager, 'getFilters')
+ .mockReturnValue(defaultFilters);
const { result, waitForNextUpdate } = renderHook(() =>
useSearchBar({
filters: defaultFilters,
- })
+ }),
);
await waitForNextUpdate();
expect(result.current.searchBarProps.filters).toMatchObject(defaultFilters);
- expect(mockDataPlugin.query.filterManager.setFilters).toBeCalledWith(defaultFilters);
- expect(mockDataPlugin.query.filterManager.getFilters).toBeCalled();
- });
-
- it('should return and preserve filters when the index pattern received is equal to the index pattern already selected in the app', async () => {
- const defaultIndexFilters: Filter[] = [
- {
- query: 'something to filter',
- meta: {
- alias: 'filter-mocked',
- disabled: false,
- negate: true,
- },
- },
- ];
- jest
- .spyOn(mockDataPlugin.indexPatterns, 'getDefault')
- .mockResolvedValue(mockedDefaultIndexPatternData);
- jest
- .spyOn(mockDataPlugin.indexPatterns, 'get')
- .mockResolvedValue(mockedDefaultIndexPatternData);
- jest
- .spyOn(mockDataPlugin.query.filterManager, 'getFilters')
- .mockReturnValue(defaultIndexFilters);
- const { result, waitForNextUpdate } = renderHook(() =>
- useSearchBar({
- defaultIndexPatternID: mockedDefaultIndexPatternData.id,
- })
+ expect(mockDataPlugin.query.filterManager.setFilters).toBeCalledWith(
+ defaultFilters,
);
- await waitForNextUpdate();
- expect(result.current.searchBarProps.indexPatterns).toMatchObject([
- mockedDefaultIndexPatternData,
- ]);
- expect(result.current.searchBarProps.filters).toMatchObject(defaultIndexFilters);
+ expect(mockDataPlugin.query.filterManager.getFilters).toBeCalled();
});
it('should return empty filters when the index pattern is NOT equal to the default app index pattern', async () => {
@@ -204,11 +193,13 @@ describe('[hook] useSearchBarConfiguration', () => {
jest
.spyOn(mockDataPlugin.indexPatterns, 'getDefault')
.mockResolvedValue(mockedDefaultIndexPatternData);
- jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]);
+ jest
+ .spyOn(mockDataPlugin.query.filterManager, 'getFilters')
+ .mockReturnValue([]);
const { result, waitForNextUpdate } = renderHook(() =>
useSearchBar({
defaultIndexPatternID: exampleIndexPatternId,
- })
+ }),
);
await waitForNextUpdate();
expect(result.current.searchBarProps.indexPatterns).toMatchObject([
diff --git a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx
index 377f24930c..4b212f0ae4 100644
--- a/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx
+++ b/plugins/main/public/components/overview/vulnerabilities/search_bar/use_search_bar_configuration.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import {
SearchBarProps,
FilterManager,
@@ -44,6 +44,7 @@ const useSearchBarConfiguration = (
props?: tUseSearchBarProps,
): tUserSearchBarResponse => {
// dependencies
+ const SESSION_STORAGE_FILTERS_NAME = 'wazuh_persistent_searchbar_filters';
const filterManager = useFilterManager().filterManager as FilterManager;
const { filters } = useFilterManager();
const [query, setQuery] = props?.query
@@ -56,29 +57,40 @@ const useSearchBarConfiguration = (
useState();
useEffect(() => {
+ if (filters && filters.length > 0) {
+ sessionStorage.setItem(
+ SESSION_STORAGE_FILTERS_NAME,
+ JSON.stringify(filters),
+ );
+ }
initSearchBar();
+ /**
+ * When the component is disassembled, the original filters that arrived
+ * when the component was assembled are added.
+ */
+ return () => {
+ const storagePreviousFilters = sessionStorage.getItem(
+ SESSION_STORAGE_FILTERS_NAME,
+ );
+ if (storagePreviousFilters) {
+ const previousFilters = JSON.parse(storagePreviousFilters);
+ const cleanedFilters = cleanFilters(previousFilters);
+ filterManager.setFilters(cleanedFilters);
+ }
+ };
}, []);
- useEffect(() => {
- const defaultIndex = props?.defaultIndexPatternID;
- /* Filters that do not belong to the default index are filtered */
- const cleanedFilters = filters.filter(
- filter => filter.meta.index === defaultIndex,
- );
- if (cleanedFilters.length !== filters.length) {
- filterManager.setFilters(cleanedFilters);
- }
- }, [filters]);
-
/**
* Initialize the searchbar props with the corresponding index pattern and filters
*/
const initSearchBar = async () => {
setIsLoading(true);
const indexPattern = await getIndexPattern(props?.defaultIndexPatternID);
+ console.log(props?.defaultIndexPatternID, 'props?.defaultIndexPatternID');
setIndexPatternSelected(indexPattern);
- const filters = await getInitialFilters(indexPattern);
- filterManager.setFilters(filters);
+ console.log(indexPattern, 'indexPattern');
+ const initialFilters = props?.filters ?? filters;
+ filterManager.setFilters(initialFilters);
setIsLoading(false);
};
@@ -87,10 +99,16 @@ const useSearchBarConfiguration = (
* If not receive a ID return the default index from the index pattern service
* @returns
*/
+ console.log(
+ props?.defaultIndexPatternID,
+ 'props?.defaultIndexPatternID antes de getIndexPattern',
+ );
+
const getIndexPattern = async (indexPatternID?: string) => {
const indexPatternService = getDataPlugin()
.indexPatterns as IndexPatternsContract;
if (indexPatternID) {
+ console.log(indexPatternID, 'indexPatternID');
try {
return await indexPatternService.get(indexPatternID);
} catch (error) {
@@ -104,44 +122,33 @@ const useSearchBarConfiguration = (
};
/**
- * Return the initial filters considering if hook receives initial filters
- * When the default index pattern is the same like the received preserve the filters
- * @param indexPattern
+ * Return filters from filters manager.
+ * Additionally solve the known issue with the auto loaded agent.id filters from the searchbar
+ * and filters those filters that are not related to the default index pattern
* @returns
*/
- const getInitialFilters = async (indexPattern: IIndexPattern) => {
- const indexPatternService = getDataPlugin()
- .indexPatterns as IndexPatternsContract;
- let initialFilters: Filter[] = [];
- if (props?.filters) {
- return props?.filters;
- }
- if (indexPattern) {
- // get filtermanager and filters
- // if the index is the same, get filters stored
- // else clear filters
- const defaultIndexPattern =
- (await indexPatternService.getDefault()) as IIndexPattern;
- initialFilters =
- defaultIndexPattern.id === indexPattern.id
- ? filterManager.getFilters()
- : [];
- } else {
- initialFilters = [];
- }
- return initialFilters;
+ const getFilters = () => {
+ const originalFilters = filterManager ? filterManager.getFilters() : [];
+ return originalFilters.filter(
+ (filter: Filter) =>
+ filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && // remove auto loaded agent.id filters
+ filter?.meta?.index === props?.defaultIndexPatternID,
+ );
};
/**
- * Return filters from filters manager.
- * Additionally solve the known issue with the auto loaded agent.id filters from the searchbar
+ * Return cleaned filters.
+ * Clean the known issue with the auto loaded agent.id filters from the searchbar
+ * and filters those filters that are not related to the default index pattern
+ * @param previousFilters
* @returns
*/
- const getFilters = () => {
- const filters = filterManager ? filterManager.getFilters() : [];
- return filters.filter(
- filter => filter.meta.controlledBy !== AUTHORIZED_AGENTS,
- ); // remove auto loaded agent.id filters
+ const cleanFilters = (previousFilters: Filter[]) => {
+ return previousFilters.filter(
+ (filter: Filter) =>
+ filter?.meta?.controlledBy !== AUTHORIZED_AGENTS &&
+ filter?.meta?.index !== props?.defaultIndexPatternID,
+ );
};
/**
@@ -156,9 +163,24 @@ const useSearchBarConfiguration = (
dateRangeFrom: timeFilter.from,
dateRangeTo: timeFilter.to,
onFiltersUpdated: (filters: Filter[]) => {
- // its necessary execute setter to apply filters
- filterManager.setFilters(filters);
- props?.onFiltersUpdated && props?.onFiltersUpdated(filters);
+ const storagePreviousFilters = sessionStorage.getItem(
+ SESSION_STORAGE_FILTERS_NAME,
+ );
+ /**
+ * If there are persisted filters, it is necessary to add them when
+ * updating the filters in the filterManager
+ */
+ if (storagePreviousFilters) {
+ const previousFilters = JSON.parse(storagePreviousFilters);
+ const cleanedFilters = cleanFilters(previousFilters);
+ filterManager.setFilters([...cleanedFilters, ...filters]);
+
+ props?.onFiltersUpdated &&
+ props?.onFiltersUpdated([...cleanedFilters, ...filters]);
+ } else {
+ filterManager.setFilters(filters);
+ props?.onFiltersUpdated && props?.onFiltersUpdated(filters);
+ }
},
onQuerySubmit: (
payload: { dateRange: TimeRange; query?: Query },
diff --git a/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap b/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap
index f1bad4e5d4..8d16ea7342 100644
--- a/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap
+++ b/plugins/main/public/components/search-bar/query-language/__snapshots__/wql.test.tsx.snap
@@ -17,46 +17,6 @@ exports[`SearchBar component Renders correctly to match the snapshot of query la
-
diff --git a/plugins/main/public/components/search-bar/query-language/wql.test.tsx b/plugins/main/public/components/search-bar/query-language/wql.test.tsx
index 2d2a1b3171..a803a79ecf 100644
--- a/plugins/main/public/components/search-bar/query-language/wql.test.tsx
+++ b/plugins/main/public/components/search-bar/query-language/wql.test.tsx
@@ -41,10 +41,6 @@ describe('SearchBar component', () => {
const wrapper = render(
);
await waitFor(() => {
- const elementImplicitQuery = wrapper.container.querySelector(
- '.euiCodeBlock__code',
- );
- expect(elementImplicitQuery?.innerHTML).toEqual('id!=000 and ');
expect(wrapper.container).toMatchSnapshot();
});
});
diff --git a/plugins/main/public/components/search-bar/query-language/wql.tsx b/plugins/main/public/components/search-bar/query-language/wql.tsx
index 191f70b2d9..5282a6e096 100644
--- a/plugins/main/public/components/search-bar/query-language/wql.tsx
+++ b/plugins/main/public/components/search-bar/query-language/wql.tsx
@@ -1156,38 +1156,6 @@ export const WQL = {
);
}
},
- prepend: implicitQueryAsQL ? (
-
- params.setQueryLanguageConfiguration(state => ({
- ...state,
- isOpenPopoverImplicitFilter:
- !state.isOpenPopoverImplicitFilter,
- }))
- }
- iconType='filter'
- >
- {implicitQueryAsQL}
-
- }
- isOpen={
- params.queryLanguage.configuration.isOpenPopoverImplicitFilter
- }
- closePopover={() =>
- params.setQueryLanguageConfiguration(state => ({
- ...state,
- isOpenPopoverImplicitFilter: false,
- }))
- }
- >
-
- Implicit query: {implicitQueryAsQL}
-
- This query is added to the input.
-
- ) : null,
// Disable the focus trap in the EuiInputPopover.
// This causes when using the Search suggestion, the suggestion popover can be closed.
// If this is disabled, then the suggestion popover is open after a short time for this
diff --git a/plugins/main/public/components/settings/api/api-table.js b/plugins/main/public/components/settings/api/api-table.js
index b0e8a1bd60..53e6056e33 100644
--- a/plugins/main/public/components/settings/api/api-table.js
+++ b/plugins/main/public/components/settings/api/api-table.js
@@ -370,7 +370,7 @@ export const ApiTable = compose(
color="primary"
iconType="questionInCircle"
aria-label="Info about the error"
- onClick={() => this.props.copyToClipBoard(item.downReason)}
+ onClick={() => this.props.copyToClipBoard(api.error.detail)}
/>
diff --git a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js
index e7f600e6b8..3eeb3c0d62 100644
--- a/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js
+++ b/plugins/main/public/components/wz-agent-selector/wz-agent-selector.js
@@ -48,12 +48,10 @@ class WzAgentSelector extends Component {
agentTableSearch(agentIdList) {
this.closeAgentModal();
if (window.location.href.includes('/agents?')) {
- this.location.search(
- 'agent',
- store.getState().appStateReducers.currentAgentData.id
- ? String(store.getState().appStateReducers.currentAgentData.id)
- : null,
- );
+ const seletedAgent =
+ agentIdList?.[0] ||
+ store.getState().appStateReducers.currentAgentData.id;
+ this.location.search('agent', seletedAgent ? String(seletedAgent) : null);
this.route.reload();
return;
}
diff --git a/plugins/main/public/components/wz-menu/wz-menu.scss b/plugins/main/public/components/wz-menu/wz-menu.scss
index 02afe9d394..7eed9064ed 100644
--- a/plugins/main/public/components/wz-menu/wz-menu.scss
+++ b/plugins/main/public/components/wz-menu/wz-menu.scss
@@ -244,11 +244,3 @@ md-toolbar .md-button {
height: 16px !important;
margin-right: 6px;
}
-
-.WzManagementSideMenu {
- padding: 16px;
-}
-
-.WzManagementSideMenu span {
- font-size: 14px !important;
-}
diff --git a/plugins/main/public/controllers/agent/components/agents-table.js b/plugins/main/public/controllers/agent/components/agents-table.js
index 991168ebda..650b9d7c5f 100644
--- a/plugins/main/public/controllers/agent/components/agents-table.js
+++ b/plugins/main/public/controllers/agent/components/agents-table.js
@@ -41,15 +41,22 @@ import { get as getLodash } from 'lodash';
import { getCore } from '../../../kibana-services';
import { itHygiene } from '../../../utils/applications';
import { RedirectAppLinks } from '../../../../../../src/plugins/opensearch_dashboards_react/public';
-
+import { connect } from 'react-redux';
+import { compose } from 'redux';
+import { updateCurrentAgentData } from '../../../redux/actions/appStateActions';
const searchBarWQLOptions = {
implicitQuery: {
query: 'id!=000',
conjunction: ';',
},
};
-
-export const AgentsTable = withErrorBoundary(
+const mapDispatchToProps = dispatch => ({
+ updateCurrentAgentData: data => dispatch(updateCurrentAgentData(data)),
+});
+export const AgentsTable = compose(
+ withErrorBoundary,
+ connect(null, mapDispatchToProps),
+)(
class AgentsTable extends Component {
_isMount = false;
constructor(props) {
@@ -292,6 +299,7 @@ export const AgentsTable = withErrorBoundary(
}
return {
onClick: ev => {
+ this.props.updateCurrentAgentData(item);
getCore().application.navigateToApp(itHygiene.id, {
path: `#/agents?tab=welcome&agent=${item.id}`,
});
diff --git a/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js b/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js
index 2b65c18c9d..234923622b 100644
--- a/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js
+++ b/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js
@@ -40,6 +40,7 @@ import { getErrorOrchestrator } from '../../../../../react-services/common-servi
import { AgentStatus } from '../../../../../components/agents/agent-status';
import { WzRequest } from '../../../../../react-services';
import { itHygiene } from '../../../../../utils/applications';
+import { updateCurrentAgentData } from '../../../../../redux/actions/appStateActions';
class WzGroupAgentsTable extends Component {
_isMounted = false;
@@ -119,6 +120,7 @@ class WzGroupAgentsTable extends Component {
aria-label='Go to the agent'
iconType='eye'
onClick={async () => {
+ this.props.updateCurrentAgentData(item);
getCore().application.navigateToApp(itHygiene.id, {
path: `#/agents?agent=${item.id}`,
});
@@ -335,6 +337,7 @@ const mapDispatchToProps = dispatch => {
updateSortFieldAgents: sortFieldAgents =>
dispatch(updateSortFieldAgents(sortFieldAgents)),
updateReload: () => dispatch(updateReload()),
+ updateCurrentAgentData: data => dispatch(updateCurrentAgentData(data)),
};
};
diff --git a/plugins/main/public/react-services/error-management/error-factory/errors/WarningError.ts b/plugins/main/public/react-services/error-management/error-factory/errors/WarningError.ts
new file mode 100644
index 0000000000..80882c4fe7
--- /dev/null
+++ b/plugins/main/public/react-services/error-management/error-factory/errors/WarningError.ts
@@ -0,0 +1,20 @@
+import { IWazuhErrorInfo, IWazuhErrorLogOpts } from '../../types';
+import WazuhError from './WazuhError';
+
+export class WarningError extends WazuhError {
+ logOptions: IWazuhErrorLogOpts;
+ constructor(error: Error, info?: IWazuhErrorInfo) {
+ super(error, info);
+ this.logOptions = {
+ error: {
+ message: `[${this.constructor.name}]: ${error.message}`,
+ title: `An warning has occurred`,
+ error: error,
+ },
+ level: 'WARNING',
+ severity: 'BUSINESS',
+ display: true,
+ store: false,
+ };
+ }
+}
diff --git a/plugins/main/public/react-services/saved-objects.js b/plugins/main/public/react-services/saved-objects.js
index f66fc25321..518875a355 100644
--- a/plugins/main/public/react-services/saved-objects.js
+++ b/plugins/main/public/react-services/saved-objects.js
@@ -24,6 +24,8 @@ import {
} from '../../common/constants';
import { getSavedObjects } from '../kibana-services';
import { webDocumentationLink } from '../../common/services/web_documentation';
+import { ErrorFactory } from './error-management';
+import { WarningError } from './error-management/error-factory/errors/WarningError';
export class SavedObject {
/**
@@ -322,7 +324,7 @@ export class SavedObject {
{},
);
return response.data.fields;
- } catch {
+ } catch (error) {
switch (indexType) {
case WAZUH_INDEX_TYPE_MONITORING:
return FieldsMonitoring;
@@ -330,6 +332,12 @@ export class SavedObject {
return FieldsStatistics;
case WAZUH_INDEX_TYPE_ALERTS:
return KnownFields;
+ default:
+ const warningError = ErrorFactory.create(WarningError, {
+ error,
+ message: error.message,
+ });
+ throw warningError;
}
}
};
diff --git a/plugins/main/public/redux/reducers/appConfigReducers.ts b/plugins/main/public/redux/reducers/appConfigReducers.ts
index 5f3d43fd92..cc2879d52d 100644
--- a/plugins/main/public/redux/reducers/appConfigReducers.ts
+++ b/plugins/main/public/redux/reducers/appConfigReducers.ts
@@ -18,35 +18,48 @@ 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,
) => {
+ console.log('Current State:', state);
+ console.log('Action:', action);
switch (action.type) {
case 'UPDATE_APP_CONFIG_SET_IS_LOADING':
return {
...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/public/utils/applications.ts b/plugins/main/public/utils/applications.ts
index e198f0f7d9..aa675cd06b 100644
--- a/plugins/main/public/utils/applications.ts
+++ b/plugins/main/public/utils/applications.ts
@@ -76,7 +76,7 @@ export const endpointSumary = {
description: i18n.translate('wz-app-endpoints-summary-description', {
defaultMessage: 'Summary of agents and their status.',
}),
- euiIconType: 'usersRolesApp',
+ euiIconType: 'spacesApp',
order: 600,
showInOverviewApp: false,
showInAgentMenu: false,
diff --git a/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts b/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts
index 469d3ce014..390e10436b 100644
--- a/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts
+++ b/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts
@@ -76,7 +76,9 @@ describe('getUpdates function', () => {
mockedGetWazuhCore.mockImplementation(() => ({
controllers: {
WazuhHostsCtrl: jest.fn().mockImplementation(() => ({
- getHostsEntries: jest.fn().mockImplementation(() => [{ id: 'api id' }]),
+ getHostsEntries: jest
+ .fn()
+ .mockImplementation(() => [{ id: 'api id' }]),
})),
},
services: {
@@ -86,6 +88,7 @@ describe('getUpdates function', () => {
request: jest.fn().mockImplementation(() => ({
data: {
data: {
+ uuid: '7f828fd6-ef68-4656-b363-247b5861b84c',
current_version: '4.3.1',
last_available_patch: {
description:
diff --git a/plugins/wazuh-check-updates/server/services/updates/get-updates.ts b/plugins/wazuh-check-updates/server/services/updates/get-updates.ts
index 36e377fe02..7f1cf39312 100644
--- a/plugins/wazuh-check-updates/server/services/updates/get-updates.ts
+++ b/plugins/wazuh-check-updates/server/services/updates/get-updates.ts
@@ -27,7 +27,7 @@ export const getUpdates = async (checkAvailableUpdates?: boolean): Promise {
const data = {};
const method = 'GET';
- const path = '/manager/version/check';
+ const path = '/manager/version/check?force_query=true';
const options = {
apiHostID: api.id,
forceRefresh: true,
@@ -42,19 +42,40 @@ export const getUpdates = async (checkAvailableUpdates?: boolean): Promise {
+ if (update_check === false) {
+ return API_UPDATES_STATUS.DISABLED;
+ }
+
+ if (
+ last_available_major?.tag ||
+ last_available_minor?.tag ||
+ last_available_patch?.tag
+ ) {
+ return API_UPDATES_STATUS.AVAILABLE_UPDATES;
+ }
+
+ return API_UPDATES_STATUS.UP_TO_DATE;
+ };
return {
- ...update,
+ current_version,
+ update_check,
+ last_available_major,
+ last_available_minor,
+ last_available_patch,
+ last_check_date: last_check_date || undefined,
api_id: api.id,
- status,
+ status: getStatus(),
};
} catch (e: any) {
const error = {
diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts
index 4949a30685..b2cb176ccc 100644
--- a/plugins/wazuh-core/common/constants.ts
+++ b/plugins/wazuh-core/common/constants.ts
@@ -50,6 +50,11 @@ export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *';
// Wazuh vulnerabilities
export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities';
+export const WAZUH_INDEX_TYPE_VULNERABILITIES = 'vulnerabilities';
+
+// Wazuh fim
+export const WAZUH_FIM_PATTERN = 'wazuh-alerts-*';
+export const WAZUH_INDEX_TYPE_FIM = 'fim';
// Job - Wazuh initialize
export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana';
@@ -861,6 +866,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/scripts/vulnerabilities-events-injector/DIS_Template.json b/scripts/vulnerabilities-events-injector/DIS_Template.json
new file mode 100644
index 0000000000..bb43bc9ab4
--- /dev/null
+++ b/scripts/vulnerabilities-events-injector/DIS_Template.json
@@ -0,0 +1,348 @@
+{
+ "mappings": {
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "agent": {
+ "properties": {
+ "build": {
+ "properties": {
+ "original": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ecs": {
+ "properties": {
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "event": {
+ "properties": {
+ "action": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "agent_id_status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "created": {
+ "type": "date"
+ },
+ "dataset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "duration": {
+ "type": "long"
+ },
+ "end": {
+ "type": "date"
+ },
+ "hash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ingested": {
+ "type": "date"
+ },
+ "kind": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "module": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "doc_values": false,
+ "index": false,
+ "type": "keyword"
+ },
+ "outcome": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reason": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "risk_score": {
+ "type": "float"
+ },
+ "risk_score_norm": {
+ "type": "float"
+ },
+ "sequence": {
+ "type": "long"
+ },
+ "severity": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "timezone": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "host": {
+ "properties": {
+ "os": {
+ "properties": {
+ "family": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "fields": {
+ "text": {
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "kernel": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "fields": {
+ "text": {
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "platform": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "labels": {
+ "type": "object"
+ },
+ "message": {
+ "type": "text"
+ },
+ "package": {
+ "properties": {
+ "architecture": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "build_version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "checksum": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "install_scope": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "installed": {
+ "type": "date"
+ },
+ "license": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "tags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "vulnerability": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "classification": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "fields": {
+ "text": {
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "enumeration": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "report_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scanner": {
+ "properties": {
+ "vendor": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "score": {
+ "properties": {
+ "base": {
+ "type": "float"
+ },
+ "environmental": {
+ "type": "float"
+ },
+ "temporal": {
+ "type": "float"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "severity": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "codec": "best_compression",
+ "mapping": {
+ "total_fields": {
+ "limit": 1000
+ }
+ },
+ "refresh_interval": "2s"
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/vulnerabilities-events-injector/dataInjectScript.py b/scripts/vulnerabilities-events-injector/dataInjectScript.py
new file mode 100644
index 0000000000..822612c5f6
--- /dev/null
+++ b/scripts/vulnerabilities-events-injector/dataInjectScript.py
@@ -0,0 +1,233 @@
+import datetime
+from datetime import timedelta
+from opensearchpy import OpenSearch, helpers
+import random
+import json
+import os.path
+import requests
+import warnings
+
+warnings.filterwarnings("ignore")
+def generateRandomDate():
+ start_date = datetime.datetime.now()
+ end_date = start_date - timedelta(days=10)
+ random_date = start_date + (end_date - start_date) * random.random()
+ return(random_date.strftime("%Y-%m-%dT%H:%M:%S.{}Z".format(random.randint(0, 999))))
+
+def generateRandomAgent():
+ agent={}
+ agent['build'] = {'original':'build{}'.format(random.randint(0, 9999))}
+ agent['id'] = 'agent{}'.format(random.randint(0, 99))
+ agent['name'] = 'Agent{}'.format(random.randint(0, 99))
+ agent['version'] = 'v{}-stable'.format(random.randint(0, 9))
+ agent['ephemeral_id'] = '{}'.format(random.randint(0, 99999))
+ agent['type'] = random.choice(['filebeat','windows','linux','macos'])
+ return(agent)
+
+def generateRandomEvent():
+ event = {}
+ event['action'] = random.choice(['login','logout','create','delete','modify','read','write','upload','download','copy','paste','cut','move','rename','open','close','execute','run','install','uninstall','start','stop','kill','suspend','resume','sleep','wake','lock','unlock','encrypt','decrypt','compress','decompress','archive','unarchive','mount','unmount','eject','connect','disconnect','send','receive'])
+ event['agent_id_status'] = random.choice(['verified','mismatch','missing','auth_metadata_missing'])
+ event['category'] = random.choice(['authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web'])
+ event['code'] = '{}'.format(random.randint(0, 99999))
+ event['created'] = generateRandomDate()
+ event['dataset'] = random.choice(['process','file','registry','socket','dns','http','tls','alert','authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web'])
+ event['duration'] = random.randint(0, 99999)
+ new_date = generateRandomDate()
+ while new_date < event['created']:
+ new_date = generateRandomDate()
+ event['end'] = new_date
+ event['hash'] = str(hash('hash{}'.format(random.randint(0, 99999))))
+ event['id'] = '{}'.format(random.randint(0, 99999))
+ event['ingested'] = generateRandomDate()
+ event['kind'] = random.choice(['alert','asset','enrichment','event','metric','state','pipeline_error','signal'])
+ event['module'] = random.choice(['process','file','registry','socket','dns','http','tls','alert','authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web'])
+ event['original'] = 'original{}'.format(random.randint(0, 99999))
+ event['outcome'] = random.choice(['success','failure','unknown'])
+ event['provider'] = random.choice(['process','file','registry','socket','dns','http','tls','alert','authentication','authorization','configuration','communication','file','network','process','registry','storage','system','web'])
+ event['reason'] = 'This event happened due to reason{}'.format(random.randint(0, 99999))
+ event['reference'] = 'https://system.example.com/event/#{}'.format(random.randint(0, 99999))
+ event['risk_score'] = round(random.uniform(0, 10),1)
+ event['risk_score_norm'] = round(random.uniform(0, 10),1)
+ event['sequence'] = random.randint(0, 10)
+ event['severity'] = random.randint(0, 10)
+ event['start'] = generateRandomDate()
+ event['timezone'] = random.choice(['UTC','GMT','PST','EST','CST','MST','PDT','EDT','CDT','MDT'])
+ event['type'] = random.choice(['access','admin','allowed', 'change', 'connection', 'creation', 'deletion', 'denied', 'end', 'error', 'group', 'indicator', 'info', 'installation', 'protocol', 'start', 'user'])
+ event['url'] = 'http://mysystem.example.com/alert/{}'.format(random.randint(0, 99999))
+ return(event)
+
+def generateRandomHost():
+ host = {}
+ family=random.choice(['debian','ubuntu','macos','ios','android','RHEL'])
+ version='{}.{}'.format(random.randint(0, 99),random.randint(0, 99))
+ host['os'] = {
+ 'family': family,
+ 'full': family + ' ' + version,
+ 'kernel': version+'kernel{}'.format(random.randint(0, 99)),
+ 'name': family + ' ' + version,
+ 'platform': family,
+ 'type': random.choice(['windows','linux','macos','ios','android','unix']),
+ 'version': version
+ }
+ return(host)
+
+
+
+def generateRandomLabels():
+ labels = {}
+ labels['label1'] = 'label{}'.format(random.randint(0, 99))
+ labels['label2'] = 'label{}'.format(random.randint(0, 99))
+ return(labels)
+
+def generateRandomPackage():
+ package = {}
+ package['architecture'] = random.choice(['x86','x64','arm','arm64'])
+ package['build_version'] = 'build{}'.format(random.randint(0, 9999))
+ package['checksum'] = 'checksum{}'.format(random.randint(0, 9999))
+ package['description'] = 'description{}'.format(random.randint(0, 9999))
+ package['install_scope'] = random.choice(['user','system'])
+ package['install_time'] = generateRandomDate()
+ package['license'] = 'license{}'.format(random.randint(0, 9))
+ package['name'] = 'name{}'.format(random.randint(0, 99))
+ package['path'] = '/path/to/package{}'.format(random.randint(0, 99))
+ package['reference'] = 'package-reference-{}'.format(random.randint(0, 99))
+ package['size'] = random.randint(0, 99999)
+ package['type'] = random.choice(['deb','rpm','msi','pkg','app','apk','exe','zip','tar','gz','7z','rar','cab','iso','dmg','tar.gz','tar.bz2','tar.xz','tar.Z','tar.lz4','tar.sz','tar.zst'])
+ package['version'] = 'v{}-stable'.format(random.randint(0, 9))
+ return(package)
+
+def generateRandomTags():
+ tags = []
+ for i in range(0, random.randint(0, 9)):
+ new_tag = 'tag{}'.format(random.randint(0, 99))
+ while new_tag in tags:
+ new_tag = 'tag{}'.format(random.randint(0, 99))
+ tags.append('tag{}'.format(random.randint(0, 99)))
+ return(tags)
+
+def generateRandomVulnerability():
+ vulnerability = {}
+ vulnerability['category'] = random.choice(['security','config','os','package','custom'])
+ vulnerability['classification'] = 'classification{}'.format(random.randint(0, 9999))
+ vulnerability['description'] = 'description{}'.format(random.randint(0, 9999))
+ vulnerability['enumeration'] = 'CVE'
+ vulnerability['id'] = 'CVE-{}'.format(random.randint(0, 9999))
+ vulnerability['reference'] = 'https://mycve.test.org/cgi-bin/cvename.cgi?name={}'.format(vulnerability['id'])
+ vulnerability['report_id'] = 'report-{}'.format(random.randint(0, 9999))
+ vulnerability['scanner'] = {'vendor':'vendor-{}'.format(random.randint(0, 9))}
+ vulnerability['score'] = {'base':round(random.uniform(0, 10),1), 'environmental':round(random.uniform(0, 10),1), 'temporal':round(random.uniform(0, 10),1),'version':'{}'.format(round(random.uniform(0, 10),1))}
+ vulnerability['severity'] = random.choice(['Low','Medium','High','Critical'])
+ return(vulnerability)
+
+def generateRandomData(number):
+ for i in range(0, int(number)):
+ yield{
+ '@timestamp':generateRandomDate(),
+ 'agent':generateRandomAgent(),
+ 'ecs':{'version':'1.7.0'},
+ 'event':generateRandomEvent(),
+ 'host':generateRandomHost(),
+ 'labels':generateRandomLabels(),
+ 'message':'message{}'.format(random.randint(0, 99999)),
+ 'package':generateRandomPackage(),
+ 'tags':generateRandomTags(),
+ 'vulnerability':generateRandomVulnerability(),
+ }
+
+def verifyIndex(index,instance):
+ if not instance.indices.exists(index):
+ if os.path.exists('DIS_Template.json'):
+ print('\nIndex {} does not exist. Trying to create it with the template in DIS_Template.json'.format(index))
+ with open('DIS_Template.json') as templateFile:
+ template = json.load(templateFile)
+ try:
+ instance.indices.create(index=index, body=template)
+ indexExist = True
+ print('Done!')
+ except Exception as e:
+ print('Error: {}'.format(e))
+ return True
+ else:
+ notemplate=input('\nIndex {} does not exist. Template file not found. Continue without template? (y/n)'.format(index))
+ while notemplate != 'y' and notemplate != 'n':
+ notemplate=input('\nInvalid option. Continue without template? (y/n)')
+ if notemplate == 'y':
+ print('\nTrying to create index {} without template'.format(index))
+ try:
+ instance.indices.create(index=index)
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+ return True
+ return False
+
+def verifySettings():
+ verified = False
+ if os.path.exists('DIS_Settings.json'):
+ with open('DIS_Settings.json') as configFile:
+ config = json.load(configFile)
+ if 'ip' not in config or 'port' not in config or 'index' not in config or 'username' not in config or 'password' not in config:
+ print('\nDIS_Settings.json is not properly configured. Continuing without it.')
+ else:
+ verified = True
+ else:
+ print('\nDIS_Settings.json not found. Continuing without it.')
+
+ if not verified:
+ ip = input("\nEnter the IP of your Indexer: \n")
+ port = input("\nEnter the port of your Indexer: \n")
+ index = input("\nEnter the index name: \n")
+ url = 'https://{}:{}/{}/_doc'.format(ip, port, index)
+ username = input("\nUsername: \n")
+ password = input("\nPassword: \n")
+ config = json.dumps({'ip':ip,'port':port,'index':index,'username':username,'password':password})
+ store = input("\nDo you want to store these settings for future use? (y/n) \n")
+ while store != 'y' and store != 'n':
+ store = input("\nInvalid option.\n Do you want to store these settings for future use? (y/n) \n")
+ if store == 'y':
+ with open('\nDIS_Settings.json', 'w') as configFile:
+ configFile.write(config)
+ return config
+
+def injectEvents(generator):
+ config = verifySettings()
+
+ instance = OpenSearch([{'host':config['ip'],'port':config['port']}], http_auth=(config['username'], config['password']), use_ssl=True, verify_certs=False)
+
+ if not instance.ping():
+ print('\nError: Could not connect to the indexer')
+ return
+
+ if (verifyIndex(config['index'],instance)):
+ print('\nTrying to inject the generated data...\n')
+ try:
+ helpers.bulk(instance, generator, index=config['index'])
+ print('\nDone!')
+ except Exception as e:
+ print('\nError: {}'.format(e))
+ return
+
+
+def main():
+ action = input("Do you want to inject data or save it to a file? (i/s) \n")
+ while(action != 'i' and action != 's'):
+ action = input("\nInvalid option.\n Do you want to inject data or save it to a file? (i/s) \n")
+ number = input("\nHow many events do you want to generate? \n")
+ while(not number.isdigit()):
+ number = input("Invalid option.\n How many events do you want to generate? \n")
+ data = generateRandomData(number)
+ if action == 's':
+ print('\nGenerating {} events...\n'.format(number))
+ outfile = open('generatedData.json','a')
+ for i in data:
+ json.dump(i, outfile)
+ outfile.write('\n')
+ outfile.close()
+ print('\nDone!\n')
+ else:
+ injectEvents(data)
+ return
+
+if __name__=="__main__":
+ main()
diff --git a/scripts/vulnerabilities-events-injector/readme.md b/scripts/vulnerabilities-events-injector/readme.md
new file mode 100644
index 0000000000..3770387005
--- /dev/null
+++ b/scripts/vulnerabilities-events-injector/readme.md
@@ -0,0 +1,18 @@
+# Vulnerabilities events injector
+
+This script generates random events with the Vulnerabilities format and saves them to a file or injects them to a Wazuh-Indexer/Opensearch instance.
+
+# Files
+
+- `dataInjectScript.py`: The main script file
+- `DIS_Template.json`: If the script creates a new index in the instance, it will create it with this template. If this file doesn't exist, it will create it without templates.
+- `DIS_Settings.json`: The script can save the Indexer/Opensearch connection parameters. They will be stored in this file.
+- `generatedData.json`: If the script is told to save the data to a file, it save it to this file.
+
+# Usage
+
+1. Install the requirements with `pip install -r requirements.txt`. For some Operating Systems it will fail and suggest a different way to install it (`sudo pacman -S python-xyz`, `sudo apt install python-xyz`, etc.).
+ If the package is not found in this way, we can install it running `pip install -r requirements.txt --break-system-packages` (It is recommended to avoid this option if possible)
+
+2. Run the script with `python3 dataInjectScript.py`
+3. Follow the instructions that it will show on the console.
diff --git a/scripts/vulnerabilities-events-injector/requirements.txt b/scripts/vulnerabilities-events-injector/requirements.txt
new file mode 100644
index 0000000000..7e8ce75a5b
--- /dev/null
+++ b/scripts/vulnerabilities-events-injector/requirements.txt
@@ -0,0 +1 @@
+opensearch_py==2.4.2