-
+
{
+
+
+ onFilter(
+ field,
+ FILTER_OPERATOR.IS,
+ flattened[field],
+ )
+ }
+ />
+
+ onFilter(
+ field,
+ FILTER_OPERATOR.IS_NOT,
+ flattened[field],
+ )
+ }
+ />
+
+ onFilter(field, FILTER_OPERATOR.EXISTS)
+ }
+ scripted={fieldMapping && fieldMapping.scripted}
+ />
+
{renderFields &&
renderFields?.find(
(field: string) => field?.id === displayName,
diff --git a/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-add.tsx b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-add.tsx
new file mode 100644
index 0000000000..cf405e126d
--- /dev/null
+++ b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-add.tsx
@@ -0,0 +1,76 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Any modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { FormattedMessage } from '@osd/i18n/react';
+import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@osd/i18n';
+import styles from './doc-viewer.scss';
+
+export interface Props {
+ onClick: () => void;
+ disabled: boolean;
+}
+
+export function DocViewTableRowBtnFilterAdd({
+ onClick,
+ disabled = false,
+}: Props) {
+ const tooltipContent = disabled ? (
+
+ ) : (
+
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-exists.tsx b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-exists.tsx
new file mode 100644
index 0000000000..033ee42005
--- /dev/null
+++ b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-exists.tsx
@@ -0,0 +1,84 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Any modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { FormattedMessage } from '@osd/i18n/react';
+import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@osd/i18n';
+
+export interface Props {
+ onClick: () => void;
+ disabled?: boolean;
+ scripted?: boolean;
+}
+
+export function DocViewTableRowBtnFilterExists({
+ onClick,
+ disabled = false,
+ scripted = false,
+}: Props) {
+ const tooltipContent = disabled ? (
+ scripted ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-remove.tsx b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-remove.tsx
new file mode 100644
index 0000000000..22318d3b1b
--- /dev/null
+++ b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-remove.tsx
@@ -0,0 +1,75 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Any modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { FormattedMessage } from '@osd/i18n/react';
+import { EuiToolTip, EuiButtonIcon } from '@elastic/eui';
+import { i18n } from '@osd/i18n';
+
+export interface Props {
+ onClick: () => void;
+ disabled?: boolean;
+}
+
+export function DocViewTableRowBtnFilterRemove({
+ onClick,
+ disabled = false,
+}: Props) {
+ const tooltipContent = disabled ? (
+
+ ) : (
+
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/plugins/main/public/components/common/hooks/use_async_action.ts b/plugins/main/public/components/common/hooks/use_async_action.ts
index 24ab181926..a98495fa9c 100644
--- a/plugins/main/public/components/common/hooks/use_async_action.ts
+++ b/plugins/main/public/components/common/hooks/use_async_action.ts
@@ -11,24 +11,27 @@
*/
import { useCallback, useState } from 'react';
-export function useAsyncAction(action, dependencies = []){
+export function useAsyncAction(
+ action: (...params: any[]) => any,
+ dependencies: any[] = [],
+) {
const [running, setRunning] = useState(false);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const run = useCallback(async (...params) => {
- try{
+ try {
setRunning(true);
setError(null);
setData(null);
const data = await action(...params);
setData(data);
- }catch(error){
+ } catch (error) {
setError(error);
- }finally{
+ } finally {
setRunning(false);
- };
+ }
}, dependencies);
return { data, error, running, run };
-}
\ No newline at end of file
+}
diff --git a/plugins/main/public/components/common/modules/main-agent.test.tsx b/plugins/main/public/components/common/modules/main-agent.test.tsx
new file mode 100644
index 0000000000..02ba68cd67
--- /dev/null
+++ b/plugins/main/public/components/common/modules/main-agent.test.tsx
@@ -0,0 +1,359 @@
+import React from 'react';
+import { MainModuleAgent } from './main-agent';
+import { AgentTabs } from '../../endpoints-summary/agent/agent-tabs';
+import { fireEvent, render } from '@testing-library/react';
+import { queryDataTestAttr } from '../../../../test/public/query-attr';
+
+const GENERATE_REPORT_BUTTON = 'generate-report-button';
+const ARIA_SELECTED = '[aria-selected="true"]';
+
+const REPORT_TAB = {
+ STATS: 'agent-tab-stats',
+ CONFIGURATION: 'agent-tab-configuration',
+ SOFTWARE: 'agent-tab-software',
+ NETWORK: 'agent-tab-network',
+ PROCESSES: 'agent-tab-processes',
+};
+
+jest.mock('../../../react-services/reporting', () => ({
+ ReportingService: {
+ startVis2Png: jest.fn(),
+ startConfigReport: jest.fn(),
+ },
+}));
+
+jest.mock('../data-source', () => ({
+ useDataSource: jest.fn().mockImplementation(() => ({
+ dataSource: {},
+ fetchFilters: {},
+ isLoading: false,
+ })),
+ AlertsDataSourceRepository: jest.fn(),
+ AlertsDataSource: jest.fn(),
+ __esModule: true,
+}));
+
+describe('Main Agent', () => {
+ let switchTab: jest.Mock;
+
+ beforeEach(() => {
+ switchTab = jest.fn();
+ });
+
+ describe('Agent tabs', () => {
+ it('should render agent tab overview when section is stats', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.STATS) + ARIA_SELECTED,
+ ),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)),
+ ).toBeFalsy();
+ });
+
+ it('should render agent tab overview when section is configuration', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.CONFIGURATION) + ARIA_SELECTED,
+ ),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)),
+ ).toBeFalsy();
+ });
+
+ it('should render agent tab overview when section is software', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.SOFTWARE) + ARIA_SELECTED,
+ ),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.NETWORK) + ARIA_SELECTED,
+ ),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.PROCESSES) + ARIA_SELECTED,
+ ),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)),
+ ).toBeFalsy();
+ });
+
+ it('should render agent tab overview when section is network', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.SOFTWARE) + ARIA_SELECTED,
+ ),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.NETWORK) + ARIA_SELECTED,
+ ),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.PROCESSES) + ARIA_SELECTED,
+ ),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)),
+ ).toBeFalsy();
+ });
+
+ it('should render agent tab overview when section is processes', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.SOFTWARE) + ARIA_SELECTED,
+ ),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.NETWORK) + ARIA_SELECTED,
+ ),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.PROCESSES) + ARIA_SELECTED,
+ ),
+ ).toBeTruthy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)),
+ ).toBeFalsy();
+
+ expect(
+ container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)),
+ ).toBeFalsy();
+ });
+
+ it('should be call switchTab when click on tab', () => {
+ const { container } = render(
+ ,
+ );
+
+ fireEvent.click(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.SOFTWARE),
+ ) as Element,
+ );
+
+ expect(switchTab).toHaveBeenCalledTimes(1);
+ expect(switchTab).toHaveBeenCalledWith(AgentTabs.SOFTWARE);
+
+ fireEvent.click(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.NETWORK),
+ ) as Element,
+ );
+
+ expect(switchTab).toHaveBeenCalledTimes(2);
+ expect(switchTab).toHaveBeenCalledWith(AgentTabs.NETWORK);
+
+ fireEvent.click(
+ container.querySelector(
+ queryDataTestAttr(REPORT_TAB.PROCESSES),
+ ) as Element,
+ );
+
+ expect(switchTab).toHaveBeenCalledTimes(3);
+ expect(switchTab).toHaveBeenCalledWith(AgentTabs.PROCESSES);
+ });
+ });
+
+ describe('Generate report button', () => {
+ it("shouldn't render generate report button when section is stats", () => {
+ const { container } = render(
+ ,
+ );
+
+ const generateReportButton = container.querySelector(
+ queryDataTestAttr(GENERATE_REPORT_BUTTON),
+ );
+
+ expect(generateReportButton).toBeFalsy();
+ });
+
+ it("shouldn't render generate report button when section is configuration", () => {
+ const { container } = render(
+ ,
+ );
+
+ const generateReportButton = container.querySelector(
+ queryDataTestAttr(GENERATE_REPORT_BUTTON),
+ );
+
+ expect(generateReportButton).toBeFalsy();
+ });
+
+ it('should render generate report button when section is software', () => {
+ const { container } = render(
+ ,
+ );
+
+ const generateReportButton = container.querySelector(
+ queryDataTestAttr(GENERATE_REPORT_BUTTON),
+ );
+
+ expect(generateReportButton).toBeTruthy();
+ });
+
+ it('should render generate report button when section is network', () => {
+ const { container } = render(
+ ,
+ );
+
+ const generateReportButton = container.querySelector(
+ queryDataTestAttr(GENERATE_REPORT_BUTTON),
+ );
+
+ expect(generateReportButton).toBeTruthy();
+ });
+
+ it('should render generate report button when section is processes', () => {
+ const { container } = render(
+ ,
+ );
+
+ const generateReportButton = container.querySelector(
+ queryDataTestAttr(GENERATE_REPORT_BUTTON),
+ );
+
+ expect(generateReportButton).toBeTruthy();
+ });
+ });
+});
diff --git a/plugins/main/public/components/common/modules/main-agent.tsx b/plugins/main/public/components/common/modules/main-agent.tsx
index b7d477038f..74ebf831e0 100644
--- a/plugins/main/public/components/common/modules/main-agent.tsx
+++ b/plugins/main/public/components/common/modules/main-agent.tsx
@@ -14,20 +14,13 @@ import React, { Component, Fragment } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
- EuiTitle,
EuiButtonEmpty,
+ EuiTabs,
+ EuiTab,
} from '@elastic/eui';
-import { euiThemeVars } from '@osd/ui-shared-deps/theme';
import '../../common/modules/module.scss';
import store from '../../../redux/store';
-import { FilterHandler } from '../../../utils/filter-handler';
-import { AppState } from '../../../react-services/app-state';
import { ReportingService } from '../../../react-services/reporting';
-import { WAZUH_MODULES } from '../../../../common/wazuh-modules';
-import { AgentInfo } from '../../common/welcome/agents-info';
-import { compose } from 'redux';
-import { withGlobalBreadcrumb } from '../hocs';
-import { endpointSummary } from '../../../utils/applications';
import {
AlertsDataSource,
AlertsDataSourceRepository,
@@ -37,140 +30,103 @@ import {
useDataSource,
} from '../data-source';
import { useAsyncAction } from '../hooks';
-import NavigationService from '../../../react-services/navigation-service';
+import { toTitleCase } from '../util/change-case';
+import clsx from 'clsx';
+import { AgentTabs } from '../../endpoints-summary/agent/agent-tabs';
+import { Agent } from '../../endpoints-summary/types';
export class MainModuleAgent extends Component {
props!: {
- [key: string]: any;
+ agent: Agent;
+ section: string;
+ switchTab?: (tab: string) => void;
+ selectView?: boolean;
+ tabs?: any[];
+ renderTabs?: () => JSX.Element;
+ agentsSelectionProps?: any;
};
- state: {
- selectView: Boolean;
- loadingReport: Boolean;
- switchModule: Boolean;
- showAgentInfo: Boolean;
- };
- reportingService: ReportingService;
- filterHandler: FilterHandler;
- constructor(props) {
- super(props);
- this.reportingService = new ReportingService();
- this.filterHandler = new FilterHandler(AppState.getCurrentPattern());
- this.state = {
- selectView: false,
- loadingReport: false,
- switchModule: false,
- showAgentInfo: false,
- };
- }
+ inventoryTabs = [AgentTabs.SOFTWARE, AgentTabs.NETWORK, AgentTabs.PROCESSES];
renderTitle() {
+ const { agent, section, switchTab } = this.props;
return (
-
-
-
-
-
-
-
- {
- NavigationService.getInstance().navigate(
- `/agents?tab=welcome&agent=${this.props.agent.id}`,
- );
- }}
- >
-
- {this.props.agent.name}
-
-
-
-
-
-
-
- {this.props.section === 'syscollector' && (
-
-
-
+
+
+
+ {this.inventoryTabs.includes(section) ? (
+ <>
+ {this.inventoryTabs.map(tab => (
+ switchTab?.(tab)}
+ >
+ {toTitleCase(tab)}
+
+ ))}
+ >
+ ) : (
+
+ {toTitleCase(section)}
+
)}
-
+
+
+ {[AgentTabs.SOFTWARE, AgentTabs.NETWORK, AgentTabs.PROCESSES].includes(
+ section,
+ ) && (
+
+
+
+ )}
);
}
render() {
const { agent, section, selectView } = this.props;
- const ModuleTabView = (this.props.tabs || []).find(
- tab => tab.id === selectView,
- );
+ const ModuleTabView = this.props.tabs?.find(tab => tab.id === selectView);
+
+ const hasTabs = this.props.tabs?.length;
+
return (
-
- {agent && agent.os && (
+
+ {agent?.os && (
-
- {this.state.showAgentInfo && (
-
- )}
- {this.props.tabs && this.props.tabs.length && (
+
+ {hasTabs && (
- {this.props.renderTabs()}
+ {this.props.renderTabs?.()}
- {ModuleTabView &&
- ModuleTabView.buttons &&
- ModuleTabView.buttons.map(
- (ModuleViewButton, index) =>
- typeof ModuleViewButton !== 'string' ? (
-
-
-
- ) : null,
- )}
+ {ModuleTabView?.buttons?.map(
+ (ModuleViewButton, index) =>
+ typeof ModuleViewButton !== 'string' ? (
+
+
+
+ ) : null,
+ )}
@@ -178,9 +134,8 @@ export class MainModuleAgent extends Component {
)}
- {!['syscollector', 'configuration'].includes(section) &&
- ModuleTabView &&
- ModuleTabView.component && (
+ {[AgentTabs.STATS].includes(section) &&
+ ModuleTabView?.component && (
)}
@@ -190,41 +145,6 @@ export class MainModuleAgent extends Component {
}
}
-export default compose(
- withGlobalBreadcrumb(({ agent, section }) => {
- if (section === 'welcome') {
- return [
- {
- text: endpointSummary.breadcrumbLabel,
- href: NavigationService.getInstance().getUrlForApp(
- endpointSummary.id,
- {
- path: `#/agents-preview`,
- },
- ),
- },
- { text: agent.id },
- ];
- } else {
- return [
- {
- text: endpointSummary.breadcrumbLabel,
- href: NavigationService.getInstance().getUrlForApp(
- endpointSummary.id,
- {
- path: `#/agents-preview`,
- },
- ),
- },
- { agent: agent },
- {
- text: WAZUH_MODULES[section].title,
- },
- ];
- }
- }),
-)(MainModuleAgent);
-
export class AgentInventoryDataSource extends AlertsDataSource {
constructor(id: string, title: string) {
super(id, title);
@@ -238,7 +158,7 @@ export class AgentInventoryDataSource extends AlertsDataSource {
}
}
-const GenerateSyscollectorReportButton = ({ agent }) => {
+const GenerateReportButton = ({ agent }: { agent: Agent }) => {
const {
dataSource,
fetchFilters,
@@ -254,7 +174,7 @@ const GenerateSyscollectorReportButton = ({ agent }) => {
(agent || store.getState().appStateReducers.currentAgentData || {}).id ||
false;
await reportingService.startVis2Png('syscollector', agentID, {
- indexPattern: dataSource.indexPattern,
+ indexPattern: dataSource?.indexPattern,
query: { query: '', language: 'kuery' },
filters: fetchFilters,
time: {
@@ -266,6 +186,7 @@ const GenerateSyscollectorReportButton = ({ agent }) => {
return (
{ModuleTabView &&
diff --git a/plugins/main/public/components/common/modules/module.scss b/plugins/main/public/components/common/modules/module.scss
index 8182b3d601..dbbd4491f1 100644
--- a/plugins/main/public/components/common/modules/module.scss
+++ b/plugins/main/public/components/common/modules/module.scss
@@ -7,14 +7,9 @@
background: #fafbfd;
}
-.wz-module-header-agent {
- height: 50px;
- padding: 16px;
-}
-
-.wz-module-header-agent .euiHealth svg {
- width: 16px;
- height: 24px;
+.wz-module-header-agent .euiTab__content {
+ font-size: 16px !important;
+ font-weight: 400;
}
.wzApp .euiFlyoutBody .euiFlyoutBody__overflowContent {
@@ -25,6 +20,10 @@
margin-top: -16px;
}
+.wz-module-header-agent > .euiFlexGroup {
+ margin-top: -6px;
+}
+
.wz-module-header-nav > .euiFlexGroup .euiFlexItem {
margin-top: 0px;
}
@@ -39,20 +38,9 @@
border-radius: 100px;
}
-.wz-module-header-agent-title-btn {
- cursor: pointer;
-}
-
.wz-font-weight-normal {
font-weight: normal;
}
-.wz-module-header-agent h1 {
- font-weight: 400;
-}
-
-.wz-module-header-agent h1 b {
- font-weight: 500;
-}
.wz-module-header-nav .euiTabs {
padding-top: 6px;
@@ -96,11 +84,6 @@ discover-app-w .sidebar-container {
@media only screen and (max-width: 767px) {
.wz-module-header-agent {
height: auto;
- h1 {
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- }
}
.wz-module-body {
margin-top: -160px;
diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx
index 0fc4b4b5bc..215c3dd14f 100644
--- a/plugins/main/public/components/common/modules/modules-defaults.tsx
+++ b/plugins/main/public/components/common/modules/modules-defaults.tsx
@@ -444,7 +444,13 @@ export const ModulesDefaults = {
],
availableFor: ['manager', 'agent'],
},
- syscollector: {
+ software: {
+ notModule: true,
+ },
+ network: {
+ notModule: true,
+ },
+ processes: {
notModule: true,
},
configuration: {
diff --git a/plugins/main/public/components/common/ribbon/ribbon-item.scss b/plugins/main/public/components/common/ribbon/ribbon-item.scss
new file mode 100644
index 0000000000..bf543a6a40
--- /dev/null
+++ b/plugins/main/public/components/common/ribbon/ribbon-item.scss
@@ -0,0 +1,4 @@
+.wz-ribbon-item .euiStat .euiText {
+ font-size: 12px;
+ font-family: sans-serif;
+}
diff --git a/plugins/main/public/components/common/ribbon/ribbon-item.tsx b/plugins/main/public/components/common/ribbon/ribbon-item.tsx
new file mode 100644
index 0000000000..29741c8a6b
--- /dev/null
+++ b/plugins/main/public/components/common/ribbon/ribbon-item.tsx
@@ -0,0 +1,148 @@
+import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
+import React from 'react';
+import { WAZUH_AGENTS_OS_TYPE } from '../../../../common/constants';
+import { AgentStatus } from '../../agents/agent-status';
+import { Agent } from '../../endpoints-summary/types';
+import { WzStat } from '../../wz-stat';
+import { GroupTruncate } from '../util/agent-group-truncate';
+import WzTextWithTooltipIfTruncated from '../wz-text-with-tooltip-if-truncated';
+import './ribbon-item.scss';
+import { getAgentOSType } from '../../../react-services';
+
+const FONT_SIZE = 12;
+
+export enum RibbonItemLabel {
+ GROUPS = 'groups',
+ OPERATING_SYSTEM = 'operating-system',
+ AGENT_STATUS = 'agent-status',
+}
+
+export type IRibbonItem = {
+ key: React.Key;
+ label: LABEL;
+ value: VALUE;
+ style?: React.CSSProperties;
+ isLoading?: boolean;
+ icon?: React.ReactNode;
+};
+
+const isGroups = (
+ item: IRibbonItem,
+): item is IRibbonItem => {
+ return item.key === RibbonItemLabel.GROUPS;
+};
+
+const isOperatingSystem = (
+ item: IRibbonItem,
+): item is IRibbonItem => {
+ return item.key === RibbonItemLabel.OPERATING_SYSTEM;
+};
+
+const isStatus = (
+ item: IRibbonItem,
+): item is IRibbonItem => {
+ return item.key === RibbonItemLabel.AGENT_STATUS;
+};
+
+interface RibbonItemProps {
+ item: IRibbonItem;
+}
+
+const WzRibbonItem = (props: RibbonItemProps) => {
+ const { item } = props;
+
+ const elementStyle = { ...(item.style || {}), fontSize: FONT_SIZE };
+ const wzWidth100 = { anchorClassName: 'wz-width-100' };
+ const tooltipProps = item.key === 'cluster-node' ? wzWidth100 : {};
+
+ const getPlatformIcon = (agent?: Agent) => {
+ let icon = '';
+ let osType = getAgentOSType(agent);
+ if (osType === WAZUH_AGENTS_OS_TYPE.DARWIN) {
+ icon = 'apple';
+ } else if (
+ [WAZUH_AGENTS_OS_TYPE.WINDOWS, WAZUH_AGENTS_OS_TYPE.LINUX].includes(
+ osType,
+ )
+ ) {
+ icon = osType;
+ }
+
+ if (icon) {
+ return (
+
+ );
+ }
+ return <>>;
+ };
+
+ const getOsName = (agent?: Agent) => {
+ const { name, version } = agent?.os || {};
+
+ if (!name && !version) return '-';
+
+ if (!version) return name;
+
+ if (!name) return version;
+
+ return `${name} ${version}`;
+ };
+
+ const renderField = function (field?: T): T | string {
+ return field !== undefined || field ? field : '-';
+ };
+
+ const renderValue = () => {
+ return item.isLoading ? (
+
+ ) : isGroups(item) && item.value?.length ? (
+
+ ) : isOperatingSystem(item) ? (
+
+ {getPlatformIcon(item.value)} {getOsName(item.value)}
+
+ ) : isStatus(item) ? (
+
+ ) : (
+
+ {item.icon} {renderField(item.value)}
+
+ );
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default WzRibbonItem;
diff --git a/plugins/main/public/components/common/ribbon/ribbon.tsx b/plugins/main/public/components/common/ribbon/ribbon.tsx
new file mode 100644
index 0000000000..50def4a9a6
--- /dev/null
+++ b/plugins/main/public/components/common/ribbon/ribbon.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { EuiFlexGroup, EuiPanel } from '@elastic/eui';
+import WzRibbonItem, { type IRibbonItem } from './ribbon-item';
+
+interface RibbonProps {
+ items: Array;
+ 'data-test-subj'?: string;
+}
+
+const WzRibbon = (props: RibbonProps) => {
+ const { items, 'data-test-subj': dataTestSubj } = props;
+ return (
+
+
+ {items.map(item => (
+
+ ))}
+
+
+ );
+};
+
+export default WzRibbon;
diff --git a/plugins/main/public/components/common/search-bar/set-filters.ts b/plugins/main/public/components/common/search-bar/set-filters.ts
new file mode 100644
index 0000000000..955bfb2934
--- /dev/null
+++ b/plugins/main/public/components/common/search-bar/set-filters.ts
@@ -0,0 +1,25 @@
+import {
+ Filter,
+ FilterManager,
+} from '../../../../../../src/plugins/data/public';
+
+const isSameNegatedFilter = (filter: Filter, prevFilter: Filter) => {
+ return (
+ filter.meta.key === prevFilter.meta.key &&
+ filter.meta.type === prevFilter.meta.type &&
+ filter.meta.params === prevFilter.meta.params.query &&
+ filter.meta.negate !== prevFilter.meta.negate
+ );
+};
+
+export const setFilters =
+ (filterManager: FilterManager) => (filters: Filter[]) => {
+ const prevFilters = filterManager
+ .getFilters()
+ .filter(
+ prevFilter =>
+ !filters.find(filter => isSameNegatedFilter(filter, prevFilter)),
+ );
+ const newFilters = [...filters, ...prevFilters];
+ filterManager.setFilters(newFilters, undefined);
+ };
diff --git a/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap b/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap
index ea7f0ada78..510ca1556d 100644
--- a/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap
+++ b/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap
@@ -577,6 +577,7 @@ exports[`Table With Search Bar component renders correctly to match the snapshot
},
]
}
+ data-test-subj="table-with-search-bar"
items={Array []}
loading={true}
noItemsMessage="No items found"
@@ -608,6 +609,7 @@ exports[`Table With Search Bar component renders correctly to match the snapshot
>
diff --git a/plugins/main/public/components/common/tables/__snapshots__/table-wz-api.test.tsx.snap b/plugins/main/public/components/common/tables/__snapshots__/table-wz-api.test.tsx.snap
index 78b4b7a1f2..d996886d77 100644
--- a/plugins/main/public/components/common/tables/__snapshots__/table-wz-api.test.tsx.snap
+++ b/plugins/main/public/components/common/tables/__snapshots__/table-wz-api.test.tsx.snap
@@ -75,10 +75,12 @@ exports[`Table WZ API component renders correctly to match the snapshot 1`] = `
className="euiFlexItem euiFlexItem--flexGrowZero wz-flex-basis-auto"
>
Table
diff --git a/plugins/main/public/components/common/tables/table-with-search-bar.tsx b/plugins/main/public/components/common/tables/table-with-search-bar.tsx
index 2c551192aa..9aba51c3d7 100644
--- a/plugins/main/public/components/common/tables/table-with-search-bar.tsx
+++ b/plugins/main/public/components/common/tables/table-with-search-bar.tsx
@@ -259,6 +259,7 @@ export function TableWithSearchBar({
/>
({ ...rest }),
diff --git a/plugins/main/public/components/common/tables/table-wz-api.tsx b/plugins/main/public/components/common/tables/table-wz-api.tsx
index 50ac453b8d..196b81be8f 100644
--- a/plugins/main/public/components/common/tables/table-wz-api.tsx
+++ b/plugins/main/public/components/common/tables/table-wz-api.tsx
@@ -200,7 +200,7 @@ export function TableWzAPI({
{rest.title && (
-
+
{rest.title}{' '}
{isLoading ? (
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 47a5756f66..ca79b0c29b 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
@@ -33,8 +33,8 @@ export class GroupTruncate extends React.Component {
groups?: string[];
label: String;
length: number;
- action: String;
- filterAction: any;
+ action: 'redirect' | 'filter';
+ filterAction?: (group: string) => void;
};
constructor(props) {
@@ -45,11 +45,7 @@ export class GroupTruncate extends React.Component {
};
}
- filterAction(group) {
- this.props.filterAction(group);
- }
-
- action(index, group) {
+ action(group: string) {
switch (this.props.action) {
case 'redirect':
return NavigationService.getInstance().navigateToApp(
@@ -59,7 +55,7 @@ export class GroupTruncate extends React.Component {
},
);
case 'filter':
- return this.filterAction(group);
+ return this.props.filterAction?.(group);
default:
console.error('Wrong property in GroupTruncate component');
break;
@@ -69,7 +65,7 @@ export class GroupTruncate extends React.Component {
renderButton(auxIndex) {
return (
{
ev.stopPropagation();
@@ -84,7 +80,7 @@ export class GroupTruncate extends React.Component {
);
}
- renderBadge(group, index) {
+ renderBadge(group: string, index: number) {
return (
{
ev.stopPropagation();
- this.action(index, group);
+ this.action(group);
}}
>
{group}
diff --git a/plugins/main/public/components/common/util/change-case.ts b/plugins/main/public/components/common/util/change-case.ts
new file mode 100644
index 0000000000..e2b8d3afe2
--- /dev/null
+++ b/plugins/main/public/components/common/util/change-case.ts
@@ -0,0 +1,3 @@
+export function toTitleCase(text: string): string {
+ return text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
+}
diff --git a/plugins/main/public/components/common/util/index.ts b/plugins/main/public/components/common/util/index.ts
index 906515e7ec..2971d4bbad 100644
--- a/plugins/main/public/components/common/util/index.ts
+++ b/plugins/main/public/components/common/util/index.ts
@@ -16,3 +16,4 @@ export { TruncateHorizontalComponents } from './truncate-horizontal-components/t
export { GroupingComponents } from './grouping-components';
export * from './markdown/markdown';
export * from './wz-overlay-mask-interface';
+export * from './is-nullish';
diff --git a/plugins/main/public/components/common/util/is-nullish.ts b/plugins/main/public/components/common/util/is-nullish.ts
new file mode 100644
index 0000000000..94666d7bef
--- /dev/null
+++ b/plugins/main/public/components/common/util/is-nullish.ts
@@ -0,0 +1,3 @@
+export const isNullish = (
+ value: T | null | undefined,
+): value is null | undefined => value === null || value === undefined;
diff --git a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx
index 0da3e2e1c5..ff1413aa35 100644
--- a/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx
+++ b/plugins/main/public/components/common/wazuh-data-grid/wz-data-grid.tsx
@@ -20,10 +20,10 @@ import {
} from '../data-grid';
import { getWazuhCorePlugin } from '../../../kibana-services';
import {
+ Filter,
IndexPattern,
SearchResponse,
} from '../../../../../../src/plugins/data/public';
-import { useDocViewer } from '../doc-viewer';
import {
ErrorHandler,
ErrorFactory,
@@ -53,6 +53,8 @@ export type tWazuhDataGridProps = {
pageSize: number;
}) => void;
onChangeSorting: (sorting: { columns: any[]; onSort: any }) => void;
+ filters: Filter[];
+ setFilters: (filters: Filter[]) => void;
};
const WazuhDataGrid = (props: tWazuhDataGridProps) => {
@@ -67,6 +69,8 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => {
onChangeSorting,
query,
dateRange,
+ filters,
+ setFilters,
} = props;
const [inspectedHit, setInspectedHit] = useState(undefined);
const [isExporting, setIsExporting] = useState(false);
@@ -115,11 +119,6 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => {
onChangeSorting && onChangeSorting(sorting || []);
}, [JSON.stringify(sorting)]);
- const docViewerProps = useDocViewer({
- doc: inspectedHit,
- indexPattern: indexPattern as IndexPattern,
- });
-
const timeField = indexPattern?.timeFieldName
? indexPattern.timeFieldName
: undefined;
@@ -150,6 +149,8 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => {
}
};
+ const closeFlyoutHandler = () => setInspectedHit(undefined);
+
return (
<>
{isLoading ? : null}
@@ -178,7 +179,7 @@ const WazuhDataGrid = (props: tWazuhDataGridProps) => {
) : null}
{inspectedHit && (
-
setInspectedHit(undefined)} size='m'>
+
{
defaultTableColumns,
wzDiscoverRenderColumns,
)}
+ filters={filters}
+ setFilters={setFilters}
+ onFilter={closeFlyoutHandler}
/>
diff --git a/plugins/main/public/components/common/wazuh-discover/components/data-grid-additional-controls.tsx b/plugins/main/public/components/common/wazuh-discover/components/data-grid-additional-controls.tsx
index 943bc4bb43..1826e15a2a 100644
--- a/plugins/main/public/components/common/wazuh-discover/components/data-grid-additional-controls.tsx
+++ b/plugins/main/public/components/common/wazuh-discover/components/data-grid-additional-controls.tsx
@@ -41,11 +41,11 @@ const DiscoverDataGridAdditionalControls = (
tooltip={
totalHits && totalHits > maxEntriesPerQuery
? {
- ariaLabel: 'Warning',
+ ariaLabel: 'Info',
content: `The query results exceeded the limit of ${formatNumWithCommas(
maxEntriesPerQuery,
)} hits. Please refine your search.`,
- iconType: 'alert',
+ iconType: 'iInCircle',
position: 'top',
}
: undefined
@@ -74,7 +74,7 @@ const DiscoverDataGridAdditionalControls = (
className='euiDataGrid__controlBtn'
onClick={onHandleExportResults}
>
- Export Formated
+ Export Formatted
>
);
diff --git a/plugins/main/public/components/common/wazuh-discover/components/doc-details.tsx b/plugins/main/public/components/common/wazuh-discover/components/doc-details.tsx
index da923c4e81..c52e6c9e44 100644
--- a/plugins/main/public/components/common/wazuh-discover/components/doc-details.tsx
+++ b/plugins/main/public/components/common/wazuh-discover/components/doc-details.tsx
@@ -1,17 +1,34 @@
-import React, { useState } from 'react';
+import React from 'react';
import { useDocViewer } from '../../doc-viewer';
import DocViewer from '../../doc-viewer/doc-viewer';
-import { IndexPattern } from '../../../../../../../src/plugins/data/common';
+import {
+ Filter,
+ IndexPattern,
+} from '../../../../../../../src/plugins/data/common';
import { EuiCodeBlock, EuiFlexGroup, EuiTabbedContent } from '@elastic/eui';
-const DocDetails = ({ doc, item, indexPattern }) => {
+interface DocDetailsProps {
+ doc: any;
+ item: any;
+ indexPattern: IndexPattern;
+ filters: Filter[];
+ setFilters: (filters: Filter[]) => void;
+}
+
+const DocDetails = ({
+ doc,
+ item,
+ indexPattern,
+ filters,
+ setFilters,
+}: DocDetailsProps) => {
const docViewerProps = useDocViewer({
doc,
indexPattern: indexPattern as IndexPattern,
});
return (
-
+
{
name: 'Table',
content: (
<>
-
+
>
),
},
@@ -29,9 +50,9 @@ const DocDetails = ({ doc, item, indexPattern }) => {
content: (
{JSON.stringify(item, null, 2)}
diff --git a/plugins/main/public/components/common/wazuh-discover/components/document-view-table-and-json.tsx b/plugins/main/public/components/common/wazuh-discover/components/document-view-table-and-json.tsx
index 2592e340b9..04598fa777 100644
--- a/plugins/main/public/components/common/wazuh-discover/components/document-view-table-and-json.tsx
+++ b/plugins/main/public/components/common/wazuh-discover/components/document-view-table-and-json.tsx
@@ -1,14 +1,29 @@
import React from 'react';
import { EuiFlexItem, EuiCodeBlock, EuiTabbedContent } from '@elastic/eui';
-import { IndexPattern } from '../../../../../../../../src/plugins/data/common';
+import {
+ IndexPattern,
+ Filter,
+} from '../../../../../../../src/plugins/data/common';
import DocViewer from '../../doc-viewer/doc-viewer';
import { useDocViewer } from '../../doc-viewer';
+interface DocumentViewTableAndJsonProps {
+ document: any;
+ indexPattern: IndexPattern;
+ renderFields?: any;
+ filters: Filter[];
+ setFilters: (filters: Filter[]) => void;
+ onFilter?: () => void;
+}
+
export const DocumentViewTableAndJson = ({
document,
indexPattern,
renderFields,
-}) => {
+ filters,
+ setFilters,
+ onFilter,
+}: DocumentViewTableAndJsonProps) => {
const docViewerProps = useDocViewer({
doc: document,
indexPattern: indexPattern as IndexPattern,
@@ -22,7 +37,13 @@ export const DocumentViewTableAndJson = ({
id: 'table',
name: 'Table',
content: (
-
+
),
},
{
diff --git a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx
index cfd96a987b..8cffce3fed 100644
--- a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx
+++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx
@@ -209,6 +209,8 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => {
}
};
+ const closeFlyoutHandler = () => setInspectedHit(undefined);
+
return (
{isDataSourceLoading ? (
@@ -290,7 +292,7 @@ const WazuhDiscoverComponent = (props: WazuhDiscoverProps) => {
{inspectedHit && (
- setInspectedHit(undefined)} size='m'>
+
{
defaultTableColumns,
wzDiscoverRenderColumns,
)}
+ filters={filters}
+ setFilters={setFilters}
+ onFilter={closeFlyoutHandler}
/>
diff --git a/plugins/main/public/components/common/wazuh-discover/wz-flyout-discover.tsx b/plugins/main/public/components/common/wazuh-discover/wz-flyout-discover.tsx
index f0e2e4199f..9b05e4798b 100644
--- a/plugins/main/public/components/common/wazuh-discover/wz-flyout-discover.tsx
+++ b/plugins/main/public/components/common/wazuh-discover/wz-flyout-discover.tsx
@@ -228,11 +228,7 @@ const WazuhFlyoutDiscoverComponent = (props: WazuhDiscoverProps) => {
render: item => (
onExpandRow(item)}
- aria-label={
- itemIdToExpandedRowMap.hasOwnProperty(item[INDEX_FIELD_NAME])
- ? 'Collapse'
- : 'Expand'
- }
+ aria-label='Info'
iconType={
itemIdToExpandedRowMap.hasOwnProperty(item[INDEX_FIELD_NAME])
? 'arrowDown'
@@ -274,7 +270,13 @@ const WazuhFlyoutDiscoverComponent = (props: WazuhDiscoverProps) => {
indexPattern,
})
) : (
-
+
);
};
@@ -318,11 +320,11 @@ const WazuhFlyoutDiscoverComponent = (props: WazuhDiscoverProps) => {
results?.hits?.total &&
results?.hits?.total > MAX_ENTRIES_PER_QUERY
? {
- ariaLabel: 'Warning',
+ ariaLabel: 'Info',
content: `The query results exceeded the limit of ${formatNumWithCommas(
MAX_ENTRIES_PER_QUERY,
)} hits. Please refine your search.`,
- iconType: 'alert',
+ iconType: 'iInCircle',
position: 'top',
}
: undefined
diff --git a/plugins/main/public/components/common/welcome/agent-info/agent-info.tsx b/plugins/main/public/components/common/welcome/agent-info/agent-info.tsx
new file mode 100644
index 0000000000..984e7c5bcf
--- /dev/null
+++ b/plugins/main/public/components/common/welcome/agent-info/agent-info.tsx
@@ -0,0 +1,134 @@
+/*
+ * Wazuh app - React component for showing agent fields such as IP, ID, name,
+ * version, OS, registration date, last keep alive.
+ *
+ * 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 { WzRequest } from '../../../../react-services/wz-request';
+import { formatUIDate } from '../../../../react-services/time-service';
+import { Agent } from '../../../endpoints-summary/types';
+import { RibbonItemLabel, type IRibbonItem } from '../../ribbon/ribbon-item';
+import WzRibbon from '../../ribbon/ribbon';
+
+interface AgentInfoProps {
+ agent: Agent;
+ goGroups?: (agent: Agent, key: number) => void;
+ isCondensed?: boolean;
+}
+
+export class AgentInfo extends Component {
+ constructor(props: AgentInfoProps) {
+ super(props);
+ }
+
+ async componentDidMount() {
+ const managerVersion = await WzRequest.apiReq('GET', '/', {});
+ this.setState({
+ managerVersion: managerVersion?.data?.data?.api_version || {},
+ });
+ }
+
+ render() {
+ const { agent } = this.props;
+ let arrayStats: IRibbonItem[];
+
+ if (this.props.isCondensed) {
+ arrayStats = [
+ {
+ key: 'id',
+ value: agent.id,
+ label: 'ID',
+ style: { maxWidth: 100 },
+ },
+ {
+ key: RibbonItemLabel.AGENT_STATUS,
+ value: agent,
+ label: 'Status',
+ style: { maxWidth: 150 },
+ },
+ {
+ key: 'version',
+ value: agent.version,
+ label: 'Version',
+ style: { maxWidth: 150 },
+ },
+ {
+ key: RibbonItemLabel.OPERATING_SYSTEM,
+ value: agent,
+ label: 'Operating system',
+ style: { minWidth: 200, maxWidth: 200 },
+ },
+ ];
+ } else {
+ arrayStats = [
+ {
+ key: 'id',
+ value: agent.id,
+ label: 'ID',
+ style: { minWidth: 30 },
+ },
+ {
+ key: RibbonItemLabel.AGENT_STATUS,
+ value: agent,
+ label: 'Status',
+ style: { minWidth: 100 },
+ },
+ {
+ key: 'ip',
+ value: agent.ip,
+ label: 'IP address',
+ style: {},
+ },
+ {
+ key: 'version',
+ value: agent.version,
+ label: 'Version',
+ style: { minWidth: 100 },
+ },
+ {
+ key: RibbonItemLabel.GROUPS,
+ value: agent.group,
+ label: 'Group',
+ style: { minWidth: 150 },
+ },
+ {
+ key: RibbonItemLabel.OPERATING_SYSTEM,
+ value: agent,
+ label: 'Operating system',
+ style: { maxWidth: 150 },
+ },
+ {
+ key: 'cluster-node',
+ value:
+ agent.node_name && agent.node_name !== 'unknown'
+ ? agent.node_name
+ : '-',
+ label: 'Cluster node',
+ style: { minWidth: 120 },
+ },
+ {
+ key: 'registration-date',
+ value: formatUIDate(agent.dateAdd),
+ label: 'Registration date',
+ style: { minWidth: 180 },
+ },
+ {
+ key: 'last-keep-alive',
+ value: formatUIDate(agent.lastKeepAlive),
+ label: 'Last keep alive',
+ style: { minWidth: 180 },
+ },
+ ];
+ }
+
+ return ;
+ }
+}
diff --git a/plugins/main/public/components/common/welcome/agents-info.js b/plugins/main/public/components/common/welcome/agents-info.js
deleted file mode 100644
index e43b3dd12c..0000000000
--- a/plugins/main/public/components/common/welcome/agents-info.js
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Wazuh app - React component for showing agent fields such as IP, ID, name,
- * version, OS, registration date, last keep alive.
- *
- * 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, Fragment } from 'react';
-import { EuiFlexItem, EuiFlexGroup, EuiBadge } from '@elastic/eui';
-import { WzRequest } from '../../../react-services/wz-request';
-import { formatUIDate } from '../../../react-services/time-service';
-import WzTextWithTooltipIfTruncated from '../wz-text-with-tooltip-if-truncated';
-import { WzStat } from '../../wz-stat';
-import { GroupTruncate } from '../util/agent-group-truncate';
-import { AgentStatus } from '../../agents/agent-status';
-
-export class AgentInfo extends Component {
- constructor(props) {
- super(props);
-
- this.state = {};
- }
-
- async componentDidMount() {
- const managerVersion = await WzRequest.apiReq('GET', '/', {});
- this.setState({
- managerVersion:
- (((managerVersion || {}).data || {}).data || {}).api_version || {},
- });
- }
-
- getPlatformIcon(agent) {
- let icon = false;
- const os = (agent || {}).os;
-
- if (((os || {}).uname || '').includes('Linux')) {
- icon = 'linux';
- } else if ((os || {}).platform === 'windows') {
- icon = 'windows';
- } else if ((os || {}).platform === 'darwin') {
- icon = 'apple';
- }
-
- return (
-
- );
- }
-
- addTextPlatformRender(agent, style) {
- const checkField = field => {
- return field !== undefined ? field : '-';
- };
-
- const os_name =
- checkField(((agent || {}).os || {}).name) +
- ' ' +
- checkField(((agent || {}).os || {}).version);
-
- const osName = os_name === '- -' ? '-' : os_name;
-
- return (
-
- {this.getPlatformIcon(this.props.agent)} {osName}
-
- );
- }
-
- addGroupsRender(agent) {
- // this was rendered with a EuiHealth, but EuiHealth has a div wrapper, and this section is rendered within a tag.
tags aren't allowed within
tags.
- return (
-
- {agent.group.map((group, key) => (
- this.props.goGroups(this.props.agent, key)}
- >
- {group}
-
- ))}
-
- );
- }
-
- buildStats(items) {
- const checkField = field => {
- return field !== undefined || field ? field : '-';
- };
- const stats = items.map(item => {
- // We add tooltipProps, so that the ClusterNode and Operating System fields occupy their space and do not exceed this, overlapping with the one on the right
- const tooltipProps =
- item.description === 'Cluster node'
- ? { anchorClassName: 'wz-width-100' }
- : {};
- return (
-
-
- ) : item.description === 'Operating system' ? (
- this.addTextPlatformRender(this.props.agent, item.style)
- ) : item.description === 'Status' ? (
-
- ) : (
-
- {checkField(item.title)}
-
- )
- }
- description={item.description}
- titleSize='xs'
- />
-
- );
- });
- return stats;
- }
-
- render() {
- const { agent } = this.props;
- let arrayStats;
-
- if (this.props.isCondensed) {
- arrayStats = [
- { title: agent.id, description: 'ID', style: { maxWidth: 100 } },
- {
- title: agent.status,
- description: 'Status',
- style: { maxWidth: 150 },
- },
- {
- title: agent.version,
- description: 'Version',
- style: { maxWidth: 150 },
- },
- {
- title: agent.name,
- description: 'Operating system',
- style: { minWidth: 200, maxWidth: 200 },
- },
- ];
- } else {
- arrayStats = [
- { title: agent.id, description: 'ID', style: { minWidth: 30 } },
- {
- title: agent.status,
- description: 'Status',
- style: { minWidth: 100 },
- },
- {
- title: agent.ip,
- description: 'IP address',
- style: {},
- },
- {
- title: agent.version,
- description: 'Version',
- style: { minWidth: 100 },
- },
- { title: agent.group, description: 'Groups', style: { minWidth: 150 } },
- {
- title: agent.name,
- description: 'Operating system',
- style: { minWidth: 150 },
- },
- {
- title:
- agent.node_name && agent.node_name !== 'unknown'
- ? agent.node_name
- : '-',
- description: 'Cluster node',
- style: { minWidth: 120 },
- },
- {
- title: formatUIDate(agent.dateAdd),
- description: 'Registration date',
- style: { minWidth: 180 },
- },
- {
- title: formatUIDate(agent.lastKeepAlive),
- description: 'Last keep alive',
- style: { minWidth: 180 },
- },
- ];
- }
-
- const stats = this.buildStats(arrayStats);
-
- return (
-
-
- {stats}
-
-
- );
- }
-}
diff --git a/plugins/main/public/components/common/welcome/agents-welcome.js b/plugins/main/public/components/common/welcome/agents-welcome.js
index 43bdc85ef4..61d41e9a2e 100644
--- a/plugins/main/public/components/common/welcome/agents-welcome.js
+++ b/plugins/main/public/components/common/welcome/agents-welcome.js
@@ -31,7 +31,7 @@ import {
MitreTopTactics,
RequirementVis,
} from './components';
-import { AgentInfo } from './agents-info';
+import { AgentInfo } from './agent-info/agent-info';
import MenuAgent from './components/menu-agent';
import './welcome.scss';
import { WzDatePicker } from '../../../components/wz-date-picker';
@@ -58,6 +58,8 @@ import { EventsCount } from './dashboard/events-count';
import { IntlProvider } from 'react-intl';
import { ButtonExploreAgent } from '../../wz-agent-selector/button-explore-agent';
import NavigationService from '../../../react-services/navigation-service';
+import VulsPanel from './components/vuls_panel/vuls_welcome_panel';
+import { AgentTabs } from '../../endpoints-summary/agent/agent-tabs';
export const AgentsWelcome = compose(
withErrorBoundary,
@@ -273,7 +275,6 @@ export const AgentsWelcome = compose(
}
renderTitle() {
- const thereAreAgentSelected = Boolean(this.props.agent?.id);
// Calculate if the header buttons should display the name or only the icon to be responsive
return (
@@ -326,14 +327,17 @@ export const AgentsWelcome = compose(
-
+
this.props.switchTab('syscollector')}
+ onClick={() => this.props.switchTab(AgentTabs.SOFTWARE)}
className='wz-it-hygiene-header-button'
tooltip={
this.state.maxModules === null
@@ -348,7 +352,7 @@ export const AgentsWelcome = compose(
this.props.switchTab('stats')}
+ onClick={() => this.props.switchTab(AgentTabs.STATS)}
className='wz-it-hygiene-header-button'
tooltip={
this.state.maxModules === null
@@ -363,7 +367,7 @@ export const AgentsWelcome = compose(
this.props.switchTab('configuration')}
+ onClick={() => this.props.switchTab(AgentTabs.CONFIGURATION)}
className='wz-it-hygiene-header-button'
tooltip={
this.state.maxModules === null
@@ -388,8 +392,8 @@ export const AgentsWelcome = compose(
renderMitrePanel() {
return (
-
-
+
+
@@ -449,6 +453,8 @@ export const AgentsWelcome = compose(
render() {
const title = this.renderTitle();
+ const responsiveGroupDirection =
+ this.state.widthWindow < 1150 ? 'column' : 'row';
return (
@@ -459,22 +465,12 @@ export const AgentsWelcome = compose(
-
+
{}} />
- {(this.state.widthWindow < 1150 && (
-
-
+
+
+ {this.renderEventCountVisualization()}
+
+
+
{this.renderMitrePanel()}
- {this.renderCompliancePanel()}
-
-
-
-
-
-
-
-
- {' '}
- {/* Events count evolution */}
- {this.renderEventCountVisualization()}
-
-
-
-
- {this.renderSCALastScan()}
-
-
- )) || (
-
-
-
-
-
- {this.renderMitrePanel()}
-
- {this.renderCompliancePanel()}
-
+
+ {this.renderCompliancePanel()}
-
-
-
-
- {' '}
- {/* Events count evolution */}
- {this.renderEventCountVisualization()}
-
- {this.renderSCALastScan()}
-
-
- )}
+
+
+
+
+
+
+
+ {this.renderSCALastScan()}
+
+
+
+
+
diff --git a/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx b/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx
index f2ec64eb15..7eb4d26d5b 100644
--- a/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx
+++ b/plugins/main/public/components/common/welcome/components/fim_events_table/fim_events_table.tsx
@@ -39,7 +39,7 @@ export function FimEventsTable({ agent }) {
-
+
FIM: Recent events
diff --git a/plugins/main/public/components/common/welcome/components/index.ts b/plugins/main/public/components/common/welcome/components/index.ts
index e6b4fae312..f40a1a90d0 100644
--- a/plugins/main/public/components/common/welcome/components/index.ts
+++ b/plugins/main/public/components/common/welcome/components/index.ts
@@ -16,3 +16,4 @@ export { FimEventsTable, useTimeFilter } from './fim_events_table';
export { ScaScan } from './sca_scan';
export { MitreTopTactics } from './mitre_top';
export { RequirementVis } from './requirement_vis';
+export { VulsTopPackageTable } from './top_packages_table';
diff --git a/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx b/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx
index 69fa2f8c1f..b1f2e71e95 100644
--- a/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx
+++ b/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx
@@ -25,6 +25,12 @@ import { FlyoutTechnique } from '../../../../overview/mitre/framework/components
import { getMitreCount } from './lib';
import { useAsyncActionRunOnStart, useTimeFilter } from '../../../hooks';
import NavigationService from '../../../../../react-services/navigation-service';
+import { AppState } from '../../../../../react-services';
+import { mitreAttack } from '../../../../../utils/applications';
+import {
+ FILTER_OPERATOR,
+ PatternDataSourceFilterManager,
+} from '../../../data-source/pattern/pattern-data-source-filter-manager';
const getTacticsData = async (agentId, timeFilter) => {
return await getMitreCount(agentId, timeFilter, undefined);
@@ -103,37 +109,47 @@ const MitreTopTacticsTechniques = ({
const [showTechniqueDetails, setShowTechniqueDetails] = useState('');
- if (showTechniqueDetails) {
- const onChangeFlyout = () => {
- setShowTechniqueDetails('');
- };
-
- const openDiscover = (e, techniqueID) => {
- NavigationService.getInstance().navigateToModule(e, 'overview', {
- tab: 'mitre',
- tabView: 'discover',
- filters: { 'rule.mitre.id': techniqueID },
- });
- };
-
- const openDashboard = (e, techniqueID) => {
- NavigationService.getInstance().navigateToModule(e, 'overview', {
- tab: 'mitre',
- tabView: 'dashboard',
- filters: { 'rule.mitre.id': techniqueID },
- });
- };
- return (
- openDashboard(e, itemId)}
- openDiscover={(e, itemId) => openDiscover(e, itemId)}
- implicitFilters={[{ 'agent.id': agentId }]}
- agentId={agentId}
- onChangeFlyout={onChangeFlyout}
- currentTechnique={showTechniqueDetails}
- />
- );
- }
+ const onChangeFlyout = () => {
+ setShowTechniqueDetails('');
+ };
+
+ const goToDashboardWithFilter = async (e, techniqueID) => {
+ const indexPatternId = AppState.getCurrentPattern();
+ const filters = [
+ PatternDataSourceFilterManager.createFilter(
+ FILTER_OPERATOR.IS,
+ `rule.mitre.id`,
+ techniqueID,
+ indexPatternId,
+ ),
+ ];
+
+ const params = `tab=mitre&tabView=dashboard&agentId=${agentId}&_g=${PatternDataSourceFilterManager.filtersToURLFormat(
+ filters,
+ )}`;
+ NavigationService.getInstance().navigateToApp(mitreAttack.id, {
+ path: `#/overview?${params}`,
+ });
+ };
+
+ const goToEventsWithFilter = async (e, techniqueID) => {
+ const indexPatternId = AppState.getCurrentPattern();
+ const filters = [
+ PatternDataSourceFilterManager.createFilter(
+ FILTER_OPERATOR.IS,
+ `rule.mitre.id`,
+ techniqueID,
+ indexPatternId,
+ ),
+ ];
+
+ const params = `tab=mitre&tabView=events&agentId=${agentId}&_g=${PatternDataSourceFilterManager.filtersToURLFormat(
+ filters,
+ )}`;
+ NavigationService.getInstance().navigateToApp(mitreAttack.id, {
+ path: `#/overview?${params}`,
+ });
+ };
if (getData.running) {
return (
@@ -180,6 +196,16 @@ const MitreTopTacticsTechniques = ({
))}
+ {showTechniqueDetails && (
+ goToDashboardWithFilter(e, itemId)}
+ openDiscover={(e, itemId) => goToEventsWithFilter(e, itemId)}
+ implicitFilters={[{ 'agent.id': agentId }]}
+ agentId={agentId}
+ onChangeFlyout={onChangeFlyout}
+ currentTechnique={showTechniqueDetails}
+ />
+ )}
>
);
diff --git a/plugins/main/public/components/common/welcome/components/top_packages_table/index.ts b/plugins/main/public/components/common/welcome/components/top_packages_table/index.ts
new file mode 100644
index 0000000000..4fd81bc185
--- /dev/null
+++ b/plugins/main/public/components/common/welcome/components/top_packages_table/index.ts
@@ -0,0 +1,15 @@
+/*
+ * Wazuh app - React component building the welcome screen of an agent.
+ * version, OS, registration date, last keep alive.
+ *
+ * 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.
+ */
+
+export { VulsTopPackageTable, useTimeFilter } from './top_packages_table';
diff --git a/plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx b/plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx
new file mode 100644
index 0000000000..7ae98623c7
--- /dev/null
+++ b/plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx
@@ -0,0 +1,92 @@
+/*
+ * Wazuh app - React component building the welcome screen of an agent.
+ * version, OS, registration date, last keep alive.
+ *
+ * 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, { useState, useEffect } from 'react';
+import {
+ EuiBasicTable,
+ EuiFlexItem,
+ EuiPanel,
+ EuiSpacer,
+ EuiText,
+ EuiFlexGroup,
+} from '@elastic/eui';
+// @ts-ignore
+import { getDataPlugin } from '../../../../../kibana-services';
+import { vulnerabilityDetection } from '../../../../../utils/applications';
+import {
+ PatternDataSourceFilterManager,
+ FILTER_OPERATOR,
+} from '../../../data-source';
+import { WzLink } from '../../../../../components/wz-link/wz-link';
+
+export function VulsTopPackageTable({ agentId, items, indexPatternId }) {
+ const [sort, setSort] = useState({
+ field: 'doc_count',
+ direction: 'desc',
+ });
+
+ const columns = [
+ {
+ field: 'key',
+ name: 'Package',
+ sortable: true,
+ render: field => (
+
+ {field}
+
+ ),
+ },
+ {
+ field: 'doc_count',
+ name: 'Count',
+ sortable: true,
+ truncateText: true,
+ width: '100px',
+ },
+ ];
+
+ return (
+
+
+
+
+ Top 5 Packages
+
+
+
+
+ setSort(e.sort)}
+ itemId='top-packages-table'
+ noItemsMessage='No recent events'
+ />
+
+ );
+}
diff --git a/plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx b/plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx
new file mode 100644
index 0000000000..fcd13c4d8c
--- /dev/null
+++ b/plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx
@@ -0,0 +1,202 @@
+import {
+ EuiPanel,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ EuiToolTip,
+ EuiButtonIcon,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import React, { Fragment, useEffect, useState } from 'react';
+import { VulsTopPackageTable } from '../top_packages_table';
+import VulsSeverityStat from '../vuls_severity_stat/vuls_severity_stat';
+import {
+ PatternDataSourceFilterManager,
+ PatternDataSource,
+ tParsedIndexPattern,
+ useDataSource,
+ FILTER_OPERATOR,
+ VulnerabilitiesDataSourceRepository,
+ VulnerabilitiesDataSource,
+} from '../../../data-source';
+import { severities } from '../../../../../controllers/overview/components/last-alerts-stat/last-alerts-stat';
+import { getCore } from '../../../../../kibana-services';
+import { RedirectAppLinks } from '../../../../../../../../src/plugins/opensearch_dashboards_react/public';
+import { vulnerabilityDetection } from '../../../../../utils/applications';
+import NavigationService from '../../../../../react-services/navigation-service';
+import { WzLink } from '../../../../../components/wz-link/wz-link';
+import { withErrorBoundary } from '../../../../common/hocs';
+import { compose } from 'redux';
+import { withVulnerabilitiesStateDataSource } from '../../../../../components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern';
+
+const VulsPanelContent = ({ agent }) => {
+ const {
+ dataSource,
+ isLoading: isDataSourceLoading,
+ fetchData,
+ } = useDataSource({
+ DataSource: VulnerabilitiesDataSource,
+ repository: new VulnerabilitiesDataSourceRepository(),
+ });
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [severityStats, setSeverityStats] = useState(null);
+ const [topPackagesData, setTopPackagesData] = useState([]);
+
+ const fetchSeverityStatsData = async () => {
+ const data = await fetchData({
+ aggs: {
+ severity: {
+ terms: {
+ field: 'vulnerability.severity',
+ size: 5,
+ order: {
+ _count: 'desc',
+ },
+ },
+ },
+ },
+ });
+ setSeverityStats(data.aggregations.severity.buckets);
+ };
+
+ const fetchTopPackagesData = async () => {
+ fetchData({
+ aggs: {
+ package: {
+ terms: {
+ field: 'package.name',
+ size: 5,
+ order: {
+ _count: 'desc',
+ },
+ },
+ },
+ },
+ }).then(results => {
+ setTopPackagesData(results.aggregations.package.buckets);
+ });
+ };
+
+ useEffect(() => {
+ if (isDataSourceLoading) {
+ return;
+ }
+ setIsLoading(true);
+ Promise.all([fetchSeverityStatsData(), fetchTopPackagesData()])
+ .catch(error => {
+ console.error(error);
+ })
+ .finally(() => {
+ setIsLoading(false);
+ });
+ }, [isDataSourceLoading, agent.id]);
+
+ const getSeverityValue = severity => {
+ const value =
+ severityStats?.find(v => v.key.toUpperCase() === severity.toUpperCase())
+ ?.doc_count || '0';
+ return value ? `${value} ${severity}` : '0';
+ };
+
+ const renderSeverityStats = (severity, index) => {
+ const severityLabel = severities[severity].label;
+ const severityColor = severities[severity].color;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ };
+
+ return (
+
+
+
+ {Object.keys(severities).reverse().map(renderSeverityStats)}
+
+
+
+
+
+
+ );
+};
+
+const PanelWithVulnerabilitiesState = compose(
+ withErrorBoundary,
+ withVulnerabilitiesStateDataSource,
+)(VulsPanelContent);
+
+const VulsPanel = ({ agent }) => {
+ return (
+
+
+
+
+
+
+ Vulnerability Detection
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default VulsPanel;
diff --git a/plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx b/plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx
new file mode 100644
index 0000000000..8796d682d2
--- /dev/null
+++ b/plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import { EuiStat, EuiStatProps } from '@elastic/eui';
+
+type VulsSeverityStatProps = {
+ value: string;
+ color: string;
+ textAlign?: EuiStatProps['textAlign'];
+ statElement?: EuiStatProps['descriptionElement'];
+ isLoading: boolean;
+};
+
+export default function VulsSeverityStat({
+ value,
+ color,
+ textAlign = 'center',
+ statElement = 'h2',
+ isLoading,
+}: VulsSeverityStatProps) {
+ return (
+
+ );
+}
diff --git a/plugins/main/public/components/common/welcome/welcome.scss b/plugins/main/public/components/common/welcome/welcome.scss
index ab06214b0d..14b0c3686e 100644
--- a/plugins/main/public/components/common/welcome/welcome.scss
+++ b/plugins/main/public/components/common/welcome/welcome.scss
@@ -3,12 +3,6 @@
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;
@@ -16,15 +10,6 @@
background-color: white;
}
-.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;
-}
-
.statWithLink:hover .euiTitle {
text-decoration: underline;
}
diff --git a/plugins/main/public/components/common/wz-text-with-tooltip-if-truncated.tsx b/plugins/main/public/components/common/wz-text-with-tooltip-if-truncated.tsx
index a448b88886..bb6de7006b 100644
--- a/plugins/main/public/components/common/wz-text-with-tooltip-if-truncated.tsx
+++ b/plugins/main/public/components/common/wz-text-with-tooltip-if-truncated.tsx
@@ -13,44 +13,42 @@
import React, { Component } from 'react';
-import {
- EuiToolTip
-} from '@elastic/eui';
+import { EuiToolTip } from '@elastic/eui';
-interface IWzTextWithTooltipIfTruncated{
- children: JSX.Element
- tooltip?: string
- elementStyle?: object
- tooltipProps?: object
- position?: string // same options as EuiTooltip's position
+interface WzTextWithTooltipIfTruncatedProps extends React.PropsWithChildren {
+ tooltip?: string;
+ elementStyle?: React.CSSProperties;
+ tooltipProps?: object;
+ position?: string; // same options as EuiTooltip's position
}
-export default class WzTextWithTooltipIfTruncated extends Component {
+export default class WzTextWithTooltipIfTruncated extends Component {
state: {
withTooltip: boolean;
};
static defaultProps = {
- elementStyle: {}
- }
- timer?: ReturnType
- reference: any
- constructor(props) {
+ elementStyle: {},
+ };
+ timer?: ReturnType;
+ reference: any;
+ constructor(props: WzTextWithTooltipIfTruncatedProps) {
super(props);
this.reference = React.createRef();
this.state = {
- withTooltip: false
+ withTooltip: false,
};
}
componentDidMount() {
- this.timer = setTimeout(() => { //TODO: remove timer and setTimeout function. It is needed while this component is mounted through the AngularJS react-component directive.
+ this.timer = setTimeout(() => {
+ //TODO: remove timer and setTimeout function. It is needed while this component is mounted through the AngularJS react-component directive.
// HTML element reference with text (maybe truncated)
const reference = this.reference.current;
// HTML element clone of reference
const clone = reference.cloneNode(true);
- clone.style.display = "inline";
- clone.style.width = "auto";
- clone.style.visibility = "hidden";
- clone.style.maxWidth = "none";
+ clone.style.display = 'inline';
+ clone.style.width = 'auto';
+ clone.style.visibility = 'hidden';
+ clone.style.maxWidth = 'none';
// Insert clone in DOM appending as sibling of reference to measure both
// reference.parentNode.appendChild(clone);
// Insert clone in DOM as body child
@@ -62,23 +60,23 @@ export default class WzTextWithTooltipIfTruncated extends Component
{this.props.children || this.props.tooltip}
@@ -96,4 +94,4 @@ export default class WzTextWithTooltipIfTruncated extends Component ({
agent: state.appStateReducers?.currentAgentData,
@@ -50,9 +51,11 @@ export const AgentView = compose(
- NavigationService.getInstance().navigate(`/agents-preview`)
+ NavigationService.getInstance().navigate(
+ SECTIONS.AGENTS_PREVIEW,
+ )
}
>
Endpoint summary
@@ -65,7 +68,7 @@ export const AgentView = compose(
),
),
)(({ agent: agentData }) => {
- const { tab = 'welcome' } = useRouterSearch();
+ const { tab = AgentTabs.WELCOME } = useRouterSearch();
const navigationService = NavigationService.getInstance();
//TODO: Replace with useDatasource and useSearchBar when replace WzDatePicker with SearchBar in AgentsWelcome component
@@ -105,15 +108,39 @@ export const AgentView = compose(
return (
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap b/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap
index a75cc41a0f..c2618c9bae 100644
--- a/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap
+++ b/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap
@@ -31,6 +31,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = `
>
Agents
@@ -299,6 +300,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = `
/>
Agents
@@ -971,6 +974,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with cust
/>
Agents
@@ -1596,6 +1601,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with no p
/>
{
getRegulatoryComplianceRequirementFilter
}
{...complianceData}
+ filters={filters}
+ setFilters={setFilters}
/>
diff --git a/plugins/main/public/components/overview/compliance-table/components/requirement-flyout/requirement-flyout.tsx b/plugins/main/public/components/overview/compliance-table/components/requirement-flyout/requirement-flyout.tsx
index 607adfd9fe..0f579e8d29 100644
--- a/plugins/main/public/components/overview/compliance-table/components/requirement-flyout/requirement-flyout.tsx
+++ b/plugins/main/public/components/overview/compliance-table/components/requirement-flyout/requirement-flyout.tsx
@@ -35,9 +35,13 @@ import { WazuhFlyoutDiscover } from '../../../../common/wazuh-discover/wz-flyout
import { PatternDataSource } from '../../../../common/data-source';
import { formatUIDate } from '../../../../../react-services';
import TechniqueRowDetails from '../../../mitre/framework/components/techniques/components/flyout-technique/technique-row-details';
-import { buildPhraseFilter } from '../../../../../../../../src/plugins/data/common';
+import {
+ buildPhraseFilter,
+ Filter,
+} from '../../../../../../../../src/plugins/data/common';
import { connect } from 'react-redux';
import { wzDiscoverRenderColumns } from '../../../../common/wazuh-discover/render-columns';
+import { setFilters } from '../../../../common/search-bar/set-filters';
const mapStateToProps = state => ({
currentAgentData: state.appStateReducers.currentAgentData,
@@ -173,6 +177,8 @@ export const RequirementFlyout = connect(mapStateToProps)(
this.filterManager.addFilters(newFilter);
}}
+ filters={[]}
+ setFilters={setFilters(this.filterManager)}
/>
);
}
diff --git a/plugins/main/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx b/plugins/main/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx
index 79bb050d2c..5f515b75cc 100644
--- a/plugins/main/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx
+++ b/plugins/main/public/components/overview/compliance-table/components/subrequirements/subrequirements.tsx
@@ -361,6 +361,8 @@ export class ComplianceSubrequirements extends Component {
]}
openDashboard={(e, itemId) => this.openDashboard(e, itemId)}
openDiscover={(e, itemId) => this.openDiscover(e, itemId)}
+ filters={this.props.filters}
+ setFilters={this.props.setFilters}
/>
)}
diff --git a/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx b/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx
index b0eace1ea7..868693439b 100644
--- a/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx
+++ b/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx
@@ -9,7 +9,7 @@
*
* Find more information about this on the LICENSE file.
*/
-import React, { useEffect, useState, useMemo } from 'react';
+import React, { useEffect, useState, useMemo, Fragment } from 'react';
import $ from 'jquery';
import {
EuiFlyoutHeader,
@@ -39,7 +39,11 @@ import {
techniquesColumns,
agentTechniquesColumns,
} from './flyout-technique-columns';
-import { PatternDataSource } from '../../../../../../../../components/common/data-source';
+import {
+ FILTER_OPERATOR,
+ PatternDataSourceFilterManager,
+ PatternDataSource,
+} from '../../../../../../../../components/common/data-source';
import { WazuhFlyoutDiscover } from '../../../../../../../common/wazuh-discover/wz-flyout-discover';
import { tFilterParams } from '../../../../mitre';
import TechniqueRowDetails from './technique-row-details';
@@ -47,6 +51,9 @@ import { buildPhraseFilter } from '../../../../../../../../../../../src/plugins/
import store from '../../../../../../../../redux/store';
import NavigationService from '../../../../../../../../react-services/navigation-service';
import { wzDiscoverRenderColumns } from '../../../../../../../common/wazuh-discover/render-columns';
+import { AppState } from '../../../../../../../../react-services';
+import { mitreAttack } from '../../../../../../../../utils/applications';
+import { setFilters } from '../../../../../../../common/search-bar/set-filters';
type tFlyoutTechniqueProps = {
currentTechnique: string;
@@ -227,7 +234,14 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
};
const expandedRow = (props: { doc: any; item: any; indexPattern: any }) => {
- return
;
+ return (
+
+ );
};
const addRenderColumn = columns => {
@@ -250,6 +264,42 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
: addRenderColumn(techniquesColumns);
};
+ const goToTechniqueInIntelligence = async (e, currentTechnique) => {
+ const indexPatternId = AppState.getCurrentPattern();
+ const filters = [
+ PatternDataSourceFilterManager.createFilter(
+ FILTER_OPERATOR.IS,
+ `rule.mitre.id`,
+ currentTechnique,
+ indexPatternId,
+ ),
+ ];
+ const params = `tab=mitre&tabView=intelligence&tabRedirect=techniques&idToRedirect=${currentTechnique}&_g=${PatternDataSourceFilterManager.filtersToURLFormat(
+ filters,
+ )}`;
+ NavigationService.getInstance().navigateToApp(mitreAttack.id, {
+ path: `#/overview?${params}`,
+ });
+ };
+
+ const goToTacticInIntelligence = async (e, tactic) => {
+ const indexPatternId = AppState.getCurrentPattern();
+ const filters = [
+ PatternDataSourceFilterManager.createFilter(
+ FILTER_OPERATOR.IS,
+ `rule.mitre.id`,
+ tactic,
+ indexPatternId,
+ ),
+ ];
+ const params = `tab=mitre&tabView=intelligence&tabRedirect=tactics&idToRedirect=${
+ tactic.id
+ }&_g=${PatternDataSourceFilterManager.filtersToURLFormat(filters)}`;
+ NavigationService.getInstance().navigateToApp(mitreAttack.id, {
+ path: `#/overview?${params}`,
+ });
+ };
+
const renderBody = () => {
const { currentTechnique } = props;
const { techniqueData } = state;
@@ -263,16 +313,7 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
>
{
- NavigationService.getInstance().navigateToModule(
- e,
- 'overview',
- {
- tab: 'mitre',
- tabView: 'intelligence',
- tabRedirect: 'techniques',
- idToRedirect: currentTechnique,
- },
- );
+ goToTechniqueInIntelligence(e, currentTechnique);
e.stopPropagation();
}}
>
@@ -286,23 +327,14 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
description: techniqueData.tactics
? techniqueData.tactics.map(tactic => {
return (
- <>
+
{
- NavigationService.getInstance().navigateToModule(
- e,
- 'overview',
- {
- tab: 'mitre',
- tabView: 'intelligence',
- tabRedirect: 'tactics',
- idToRedirect: tactic.id,
- },
- );
+ goToTacticInIntelligence(e, tactic);
e.stopPropagation();
}}
>
@@ -310,7 +342,7 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
- >
+
);
})
: '',
@@ -324,12 +356,12 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
Technique details
}
- initialIsOpen={true}
>
@@ -397,7 +429,7 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
DataSource={PatternDataSource}
tableColumns={getDiscoverColumns()}
filterManager={filterManager}
- initialFetchFilters={filterParams.filters}
+ initialFetchFilters={filterParams?.filters || []}
expandedRowComponent={expandedRow}
/>
diff --git a/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/technique-row-details.tsx b/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/technique-row-details.tsx
index b808662c8e..c07de35380 100644
--- a/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/technique-row-details.tsx
+++ b/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/technique-row-details.tsx
@@ -3,17 +3,29 @@ import { EuiCodeBlock, EuiFlexGroup, EuiTabbedContent } from '@elastic/eui';
import { useDocViewer } from '../../../../../../../common/doc-viewer/use-doc-viewer';
import DocViewer from '../../../../../../../common/doc-viewer/doc-viewer';
import RuleDetails from '../rule-details';
-import { IndexPattern } from '../../../../../../../../../../../src/plugins/data/common';
+import {
+ IndexPattern,
+ Filter,
+} from '../../../../../../../../../../../src/plugins/data/common';
import { WzRequest } from '../../../../../../../../react-services/wz-request';
-type Props = {
+type TechniqueRowDetailsProps = {
doc: any;
item: any;
indexPattern: IndexPattern;
onRuleItemClick?: (value: any, indexPattern: IndexPattern) => void;
+ filters: Filter[];
+ setFilters: (filters: Filter[]) => void;
};
-const TechniqueRowDetails = ({ doc, item, indexPattern, onRuleItemClick }) => {
+const TechniqueRowDetails = ({
+ doc,
+ item,
+ indexPattern,
+ onRuleItemClick,
+ filters,
+ setFilters,
+}: TechniqueRowDetailsProps) => {
const docViewerProps = useDocViewer({
doc,
indexPattern: indexPattern as IndexPattern,
@@ -23,13 +35,16 @@ const TechniqueRowDetails = ({ doc, item, indexPattern, onRuleItemClick }) => {
const getRuleData = async () => {
const params = { q: `id=${item.rule.id}` };
- const rulesDataResponse = await WzRequest.apiReq('GET', `/rules`, { params });
- const ruleData = ((rulesDataResponse.data || {}).data || {}).affected_items[0] || {};
+ const rulesDataResponse = await WzRequest.apiReq('GET', `/rules`, {
+ params,
+ });
+ const ruleData =
+ ((rulesDataResponse.data || {}).data || {}).affected_items[0] || {};
setRuleData(ruleData);
};
const onAddFilter = (filter: { [key: string]: string }) => {
- onRuleItemClick(filter, indexPattern);
+ onRuleItemClick?.(filter, indexPattern);
};
useEffect(() => {
@@ -46,7 +61,11 @@ const TechniqueRowDetails = ({ doc, item, indexPattern, onRuleItemClick }) => {
name: 'Table',
content: (
<>
-
+
>
),
},
@@ -56,9 +75,9 @@ const TechniqueRowDetails = ({ doc, item, indexPattern, onRuleItemClick }) => {
content: (
{JSON.stringify(item, null, 2)}
diff --git a/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx b/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx
index 2533d1643d..607f2c37a0 100644
--- a/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx
+++ b/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx
@@ -183,7 +183,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
}
};
- const buildPanel = (techniqueID) => {
+ const buildPanel = techniqueID => {
return [
{
id: 0,
@@ -191,21 +191,21 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
items: [
{
name: 'Filter for value',
- icon: ,
+ icon: ,
onClick: () => {
closeActionsMenu();
},
},
{
name: 'Filter out value',
- icon: ,
+ icon: ,
onClick: () => {
closeActionsMenu();
},
},
{
name: 'View technique details',
- icon: ,
+ icon: ,
onClick: () => {
closeActionsMenu();
showFlyout(techniqueID);
@@ -218,13 +218,17 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
const techniqueColumnsResponsive = () => {
if (props && props?.windowSize) {
- return props.windowSize.width < 930 ? 2 : props.windowSize.width < 1200 ? 3 : 4;
+ return props.windowSize.width < 930
+ ? 2
+ : props.windowSize.width < 1200
+ ? 3
+ : 4;
} else {
return 4;
}
};
- const getMitreTechniques = async (params) => {
+ const getMitreTechniques = async params => {
try {
return await WzRequest.apiReq('GET', '/mitre/techniques', { params });
} catch (error) {
@@ -250,10 +254,16 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
const params = { limit: limitResults };
setIsSearching(true);
const output = await getMitreTechniques(params);
- const totalItems = (((output || {}).data || {}).data || {}).total_affected_items;
+ const totalItems = (((output || {}).data || {}).data || {})
+ .total_affected_items;
let mitreTechniques = [];
mitreTechniques.push(...output.data.data.affected_items);
- if (totalItems && output.data && output.data.data && totalItems > limitResults) {
+ if (
+ totalItems &&
+ output.data &&
+ output.data.data &&
+ totalItems > limitResults
+ ) {
const extraResults = await Promise.all(
Array(Math.ceil((totalItems - params.limit) / params.limit))
.fill()
@@ -263,7 +273,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
offset: limitResults * (1 + index),
});
return response.data.data.affected_items;
- })
+ }),
);
mitreTechniques.push(...extraResults.flat());
}
@@ -271,16 +281,17 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
setIsSearching(false);
};
- const buildObjTechniques = (techniques) => {
+ const buildObjTechniques = techniques => {
const techniquesObj = [];
- techniques.forEach((element) => {
- const mitreObj = state.mitreTechniques.find((item) => item.id === element);
+ techniques.forEach(element => {
+ const mitreObj = state.mitreTechniques.find(item => item.id === element);
if (mitreObj) {
const mitreTechniqueName = mitreObj.name;
const mitreTechniqueID =
mitreObj.source === MITRE_ATTACK
? mitreObj.external_id
- : mitreObj.references.find((item) => item.source === MITRE_ATTACK).external_id;
+ : mitreObj.references.find(item => item.source === MITRE_ATTACK)
+ .external_id;
mitreTechniqueID
? techniquesObj.push({
id: mitreTechniqueID,
@@ -292,7 +303,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
return techniquesObj;
};
- const addFilter = (filter) => {
+ const addFilter = filter => {
const { filterManager } = getDataPlugin().query;
const matchPhrase = {};
matchPhrase[filter.key] = filter.value;
@@ -333,37 +344,43 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
let hash = {};
let tacticsToRender: Array = [];
const currentTechniques = Object.keys(tacticsObject)
- .map((tacticsKey) => ({
+ .map(tacticsKey => ({
tactic: tacticsKey,
techniques: buildObjTechniques(tacticsObject[tacticsKey].techniques),
}))
- .filter((tactic) => selectedTactics[tactic.tactic])
- .map((tactic) => tactic.techniques)
+ .filter(tactic => selectedTactics[tactic.tactic])
+ .map(tactic => tactic.techniques)
.flat()
- .filter((techniqueID, index, array) => array.indexOf(techniqueID) === index);
+ .filter(
+ (techniqueID, index, array) => array.indexOf(techniqueID) === index,
+ );
tacticsToRender = currentTechniques
- .filter((technique) =>
+ .filter(technique =>
state.filteredTechniques
? state.filteredTechniques.includes(technique.id)
: technique.id && hash[technique.id]
? false
- : (hash[technique.id] = true)
+ : (hash[technique.id] = true),
)
- .map((technique) => {
+ .map(technique => {
return {
id: technique.id,
label: `${technique.id} - ${technique.name}`,
quantity:
- (techniquesCount.find((item) => item.key === technique.id) || {}).doc_count || 0,
+ (techniquesCount.find(item => item.key === technique.id) || {})
+ .doc_count || 0,
};
})
- .filter((technique) => (state.hideAlerts ? technique.quantity !== 0 : true));
+ .filter(technique =>
+ state.hideAlerts ? technique.quantity !== 0 : true,
+ );
const tacticsToRenderOrdered = tacticsToRender
.sort((a, b) => b.quantity - a.quantity)
.map((item, idx) => {
const tooltipContent = `View details of ${item.label} (${item.id})`;
const toolTipAnchorClass =
- 'wz-display-inline-grid' + (state.hover === item.id ? ' wz-mitre-width' : ' ');
+ 'wz-display-inline-grid' +
+ (state.hover === item.id ? ' wz-mitre-width' : ' ');
return (
setState({ ...state, hover: item.id })}
@@ -376,8 +393,8 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
}}
>
@@ -447,8 +470,10 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
});
if (isSearching || loadingAlerts || isLoading) {
return (
-
-
+
+
);
}
@@ -456,7 +481,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
return (
{
);
} else {
return (
-
+
);
}
};
- const onChange = (searchValue) => {
+ const onChange = searchValue => {
if (!searchValue) {
setState({ ...state, filteredTechniques: false });
setIsSearching(false);
}
};
- const onSearch = async (searchValue) => {
+ const onSearch = async searchValue => {
try {
if (searchValue) {
setIsSearching(true);
@@ -490,8 +519,12 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
search: searchValue,
},
});
- const filteredTechniques = (((response || {}).data || {}).data.affected_items || []).map(
- (item) => [item].filter((reference) => reference.source === MITRE_ATTACK)[0].external_id
+ const filteredTechniques = (
+ ((response || {}).data || {}).data.affected_items || []
+ ).map(
+ item =>
+ [item].filter(reference => reference.source === MITRE_ATTACK)[0]
+ .external_id,
);
setState({
...state,
@@ -525,7 +558,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
setState({ ...state, actionsOpen: false });
};
- const showFlyout = (techniqueData) => {
+ const showFlyout = techniqueData => {
setState({
...state,
isFlyoutVisible: true,
@@ -545,7 +578,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
-
+
Techniques
@@ -555,23 +588,27 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
Hide techniques with no alerts
- hideAlerts()} />
+ hideAlerts()}
+ />
-
+
-
+
{renderFacet()}
@@ -580,12 +617,15 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
onChangeFlyout={onChangeFlyout}
currentTechnique={currentTechnique}
filterParams={{
- filters: [...filterParams.filters, ...getMitreRuleIdFilter(currentTechnique)], // the flyout must receive the filters from the mitre global search bar
+ filters: [
+ ...filterParams.filters,
+ ...getMitreRuleIdFilter(currentTechnique),
+ ], // the flyout must receive the filters from the mitre global search bar
query: filterParams.query,
time: filterParams.time,
}}
- openDashboard={(e) => openDashboard(e, currentTechnique)}
- openDiscover={(e) => openDiscover(e, currentTechnique)}
+ openDashboard={e => openDashboard(e, currentTechnique)}
+ openDiscover={e => openDiscover(e, currentTechnique)}
/>
)}
diff --git a/plugins/main/public/components/overview/mitre/intelligence/__snapshots__/intelligence.test.tsx.snap b/plugins/main/public/components/overview/mitre/intelligence/__snapshots__/intelligence.test.tsx.snap
index 7249e54239..e3c4a08728 100644
--- a/plugins/main/public/components/overview/mitre/intelligence/__snapshots__/intelligence.test.tsx.snap
+++ b/plugins/main/public/components/overview/mitre/intelligence/__snapshots__/intelligence.test.tsx.snap
@@ -164,6 +164,7 @@ exports[`Module Mitre Att&ck intelligence container should render the component
>
Groups
@@ -243,6 +244,7 @@ exports[`Module Mitre Att&ck intelligence container should render the component
/>
{
const [agentsCounts, setAgentsCounts] = useState
({});
+ const [isAgentsLoading, setIsAgentsLoading] = useState(true);
const { tab = 'welcome', tabView = 'dashboard', agentId } = useRouterSearch();
const navigationService = NavigationService.getInstance();
const pinnedAgentManager = new PinnedAgentManager();
@@ -131,6 +132,7 @@ export const Overview: React.FC = withRouteResolvers({
},
} = await WzRequest.apiReq('GET', '/agents/summary/status', {});
setAgentsCounts(data);
+ setIsAgentsLoading(false);
} catch (error) {
return Promise.reject(error);
}
@@ -167,7 +169,7 @@ export const Overview: React.FC = withRouteResolvers({
-
+
diff --git a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx
index 6f0ed611c8..8468fa3f7a 100644
--- a/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx
+++ b/plugins/main/public/components/overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern.tsx
@@ -24,7 +24,7 @@ export async function validateVulnerabilitiesStateDataSources({
const existIndexPattern = await existsIndexPattern(indexPatternID);
const indexPattern = existIndexPattern;
- // If the idnex pattern does not exist, then check the existence of index
+ // If the index pattern does not exist, then check the existence of index
if (existIndexPattern?.error?.statusCode === HTTP_STATUS_CODES.NOT_FOUND) {
// Check the existence of indices
const { exist, fields } = await existsIndices(indexPatternID);
@@ -41,7 +41,7 @@ export async function validateVulnerabilitiesStateDataSources({
},
};
}
- // If the some index match the index pattern, then create the index pattern
+ // If some index matches the index pattern, then create the index pattern
const resultCreateIndexPattern = await createIndexPattern(
indexPatternID,
fields,
@@ -57,14 +57,6 @@ export async function validateVulnerabilitiesStateDataSources({
},
};
}
- /* WORKAROUND: Redirect to the root of Vulnerabilities Detection application that should
- redirects to the Dashboard tab. We want to redirect to this view, because we need the
- component is visible (visualizations) to ensure the process that defines the filters for the
- Events tab is run when the Dashboard component is unmounted. This workaround solves a
- problem in the Events tabs related there are no implicit filters when accessing if the HOC
- that protect the view is passed.
- */
- NavigationService.getInstance().navigateToApp(vulnerabilityDetection.id);
}
return {
ok: false,
diff --git a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.scss b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.scss
index 4844141f13..46eab9c020 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.scss
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.scss
@@ -5,11 +5,6 @@
height: max-content !important;
}
- // This makes the table not generate an unnecessary scroll when filtering data from 1 page and then do another search for more pages.
- .vulsInventoryDataGrid {
- height: calc(100vh - 216px) !important;
- }
-
.euiDataGrid--fullScreen {
height: calc(100vh - 49px);
bottom: 0;
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 e4b46f97be..2497b0af16 100644
--- a/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx
+++ b/plugins/main/public/components/overview/vulnerabilities/dashboards/inventory/inventory.tsx
@@ -203,6 +203,8 @@ const InventoryVulsComponent = () => {
getUnderEvaluation(filters || []),
);
+ const closeFlyoutHandler = () => setInspectedHit(undefined);
+
return (
<>
@@ -262,13 +264,13 @@ const InventoryVulsComponent = () => {
results?.hits?.total &&
results?.hits?.total > MAX_ENTRIES_PER_QUERY
? {
- ariaLabel: 'Warning',
+ ariaLabel: 'Info',
content: `The query results has exceeded the limit of ${formatNumWithCommas(
MAX_ENTRIES_PER_QUERY,
)} hits. To provide a better experience the table only shows the first ${formatNumWithCommas(
MAX_ENTRIES_PER_QUERY,
)} hits.`,
- iconType: 'alert',
+ iconType: 'iInCircle',
position: 'top',
}
: undefined
@@ -286,7 +288,7 @@ const InventoryVulsComponent = () => {
className='euiDataGrid__controlBtn'
onClick={onClickExportResults}
>
- Export Formated
+ Export Formatted
>
),
@@ -296,7 +298,7 @@ const InventoryVulsComponent = () => {
) : null}
{inspectedHit && (
- setInspectedHit(undefined)} size='m'>
+
Vulnerability details
@@ -311,6 +313,9 @@ const InventoryVulsComponent = () => {
inventoryTableDefaultColumns,
wzDiscoverRenderColumns,
)}
+ filters={filters}
+ setFilters={setFilters}
+ onFilter={closeFlyoutHandler}
/>
diff --git a/plugins/main/public/components/wz-menu/wz-menu.js b/plugins/main/public/components/wz-menu/wz-menu.js
index aa3734b37c..73596ee4cb 100644
--- a/plugins/main/public/components/wz-menu/wz-menu.js
+++ b/plugins/main/public/components/wz-menu/wz-menu.js
@@ -43,17 +43,6 @@ import WzDataSourceSelector from '../common/data-source/components/wz-data-sourc
import { PinnedAgentManager } from '../wz-agent-selector/wz-agent-selector-service';
import NavigationService from '../../react-services/navigation-service';
-const sections = {
- overview: 'overview',
- manager: 'manager',
- 'agents-preview': 'agents-preview',
- agents: 'agents-preview',
- settings: 'settings',
- 'wazuh-dev': 'wazuh-dev',
- 'health-check': 'health-check',
- security: 'security',
-};
-
export const WzMenu = withWindowSize(
class WzMenu extends Component {
constructor(props) {
diff --git a/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js b/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js
index 5c27ef5f5d..3aaf1139e7 100644
--- a/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js
+++ b/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.js
@@ -71,6 +71,7 @@ import {
EuiSpacer,
EuiButtonEmpty,
EuiFlexItem,
+ EuiPageBody,
} from '@elastic/eui';
import { WzRequest } from '../../../../../react-services/wz-request';
@@ -205,271 +206,273 @@ class WzConfigurationSwitch extends Component {
const { agent } = this.props; // TODO: goGroups and exportConfiguration is used for Manager and depends of AngularJS
return (
-
- {agent.id !== '000' && agent.group && agent.group.length ? (
-
- Groups:
-
- {agent.group.map((group, key) => (
-
- {group}
-
- ))}
-
-
-
- ) : null}
- {view !== '' && view !== 'edit-configuration' && (
-
- {agent.id === '000' && (
-
-
-
- )}
-
- )}
- {view === '' &&
- ((!this.state.loadingOverview && (
-
+
+ {agent.id !== '000' && agent.group && agent.group.length ? (
+
+ Groups:
+
+ {agent.group.map((group, key) => (
+
+ {group}
+
+ ))}
+
+
+
+ ) : null}
+ {view !== '' && view !== 'edit-configuration' && (
+
- )) || )}
- {view === 'edit-configuration' && (
-
- )}
- {view !== '' && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {agent.id === '000' && (
+
+
+
+ )}
+
+ )}
+ {view === '' &&
+ ((!this.state.loadingOverview && (
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
+ )) || )}
+ {view === 'edit-configuration' && (
+
+ )}
+ {view !== '' && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
);
}
diff --git a/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.test.tsx b/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.test.tsx
new file mode 100644
index 0000000000..e58af9ffed
--- /dev/null
+++ b/plugins/main/public/controllers/management/components/management/configuration/configuration-switch.test.tsx
@@ -0,0 +1,206 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import WzConfigurationSwitch from './configuration-switch';
+import { queryDataTestAttr } from '../../../../../../test/public/query-attr';
+import { CSS } from '../../../../../../test/utils/CSS';
+
+jest.mock('react-redux', () => ({
+ connect: () => Component => Component,
+ __esModule: true,
+}));
+
+jest.mock('redux', () => ({
+ compose: () => Component => Component,
+ __esModule: true,
+}));
+
+jest.mock('./configuration-overview.js', () => () => <>>);
+
+jest.mock('./global-configuration/global-configuration', () => ({
+ WzConfigurationGlobalConfigurationManager: () => <>>,
+ WzConfigurationGlobalConfigurationAgent: () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('./edit-configuration/edit-configuration', () => () => <>>);
+
+jest.mock('./registration-service/registration-service', () => () => <>>);
+
+jest.mock('./log-settings/log-settings', () => () => <>>);
+
+jest.mock('./cluster/cluster', () => () => <>>);
+
+jest.mock('./alerts/alerts', () => () => <>>);
+
+jest.mock('./client/client', () => () => <>>);
+
+jest.mock('./client-buffer/client-buffer', () => () => <>>);
+
+jest.mock('./alerts/alerts-labels', () => ({
+ WzConfigurationAlertsLabelsAgent: () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('./integrations/integrations', () => () => <>>);
+
+jest.mock('./policy-monitoring/policy-monitoring', () => () => <>>);
+
+jest.mock('./open-scap/open-scap', () => () => <>>);
+
+jest.mock('./cis-cat/cis-cat', () => () => <>>);
+
+jest.mock('./vulnerabilities/vulnerabilities', () => () => <>>);
+
+jest.mock('./osquery/osquery', () => () => <>>);
+
+jest.mock('./inventory/inventory', () => () => <>>);
+
+jest.mock('./active-response/active-response', () => () => <>>);
+
+jest.mock('./active-response/active-response-agent', () => () => <>>);
+
+jest.mock('./commands/commands', () => () => <>>);
+
+jest.mock('./docker-listener/docker-listener', () => () => <>>);
+
+jest.mock('./log-collection/log-collection', () => () => <>>);
+
+jest.mock('./integrity-monitoring/integrity-monitoring', () => () => <>>);
+
+jest.mock('./agentless/agentless', () => () => <>>);
+
+jest.mock('./aws-s3/aws-s3', () => () => <>>);
+
+jest.mock('./azure-logs/azure-logs', () => () => <>>);
+
+jest.mock('./google-cloud-pub-sub/google-cloud-pub-sub', () => () => <>>);
+
+jest.mock('./github/github', () => ({
+ WzConfigurationGitHub: () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('./util-components/view-selector', () => ({
+ default: () => <>>,
+ WzViewSelectorSwitch: () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('./util-components/loading', () => () => <>>);
+
+jest.mock('./util-hocs/render-if', () => ({
+ withRenderIfOrWrapped: () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('./util-components/configuration-path', () => () => <>>);
+
+jest.mock('./util-components/refresh-cluster-info-button', () => () => <>>);
+
+jest.mock('./office365/office365', () => ({
+ WzConfigurationOffice365: () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('../../../../../components/agents/prompts', () => ({
+ PromptNoActiveAgentWithoutSelect: () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('@osd/i18n', () => ({
+ i18n: {
+ translate: (_, opts) => opts.defaultMessage,
+ },
+ __esModule: true,
+}));
+
+jest.mock('../../../../../utils/applications', () => ({
+ id: '000',
+ __esModule: true,
+}));
+
+jest.mock('../../../../../react-services/navigation-service', () => ({
+ getInstance: () => ({
+ navigate: () => {},
+ getPathname: () => '',
+ __esModule: true,
+ }),
+ __esModule: true,
+}));
+
+jest.mock('../../../../../components/common/hocs', () => ({
+ withUserAuthorizationPrompt: () => () => <>>,
+ __esModule: true,
+}));
+
+jest.mock('../../../../../react-services/wz-request', () => ({
+ WzRequest: {
+ apiReq: jest.fn().mockResolvedValue({
+ data: {
+ data: {
+ affected_items: [],
+ },
+ },
+ }),
+ __esModule: true,
+ },
+}));
+
+jest.mock('./utils/wz-fetch', () => ({
+ clusterReq: jest.fn().mockResolvedValue({
+ data: {
+ data: {
+ affected_items: [],
+ },
+ },
+ }),
+ clusterNodes: jest.fn().mockResolvedValue({
+ data: {
+ data: {
+ affected_items: [],
+ },
+ },
+ }),
+}));
+
+describe('WzConfigurationSwitch', () => {
+ let updateClusterNodes: jest.Mock;
+ let updateClusterNodeSelected: jest.Mock;
+
+ beforeEach(() => {
+ updateClusterNodes = jest.fn();
+ updateClusterNodeSelected = jest.fn();
+ });
+
+ it("shouldn't render the agent info ribbon", () => {
+ const { container } = render(
+ ,
+ );
+
+ const agentInfoRibbon = container.querySelector(
+ queryDataTestAttr('agent-info'),
+ );
+
+ expect(agentInfoRibbon).toBeFalsy();
+ });
+
+ it("shouldn't render any ribbon items", () => {
+ const { container } = render(
+ ,
+ );
+
+ const ribbonItems = container.querySelectorAll(
+ queryDataTestAttr('ribbon-item', CSS.Attribute.Substring),
+ );
+
+ expect(ribbonItems.length).toBe(0);
+ });
+});
diff --git a/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx b/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx
index 4f51cf8600..cf9258ca81 100644
--- a/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx
+++ b/plugins/main/public/controllers/overview/components/last-alerts-stat/last-alerts-stat.tsx
@@ -5,6 +5,7 @@ import {
EuiLink,
EuiToolTip,
EuiText,
+ EuiStatProps,
} from '@elastic/eui';
import { getLast24HoursAlerts } from './last-alerts-service';
import { UI_COLOR_STATUS } from '../../../../../common/constants';
@@ -22,7 +23,7 @@ import {
type SeverityKey = 'low' | 'medium' | 'high' | 'critical';
-const severities = {
+export const severities = {
low: {
label: 'Low',
color: UI_COLOR_STATUS.success,
@@ -59,8 +60,14 @@ const severities = {
export function LastAlertsStat({
severity: severityKey,
+ hideBottomText,
+ direction = 'row',
+ textAlign = 'center',
}: {
severity: SeverityKey;
+ hideBottomText?: boolean;
+ direction: 'row' | 'column';
+ textAlign?: EuiStatProps['textAlign'];
}) {
const [countLastAlerts, setCountLastAlerts] = useState(null);
const [discoverLocation, setDiscoverLocation] = useState('');
@@ -117,6 +124,13 @@ export function LastAlertsStat({
getCountLastAlerts();
}, []);
+ const statDescription =
+ direction === 'row' ? `${severity.label} severity` : '';
+ const statValue =
+ direction === 'row'
+ ? `${countLastAlerts ?? '-'}`
+ : ` ${countLastAlerts ?? '-'} ${severity.label}`;
+
return (
@@ -141,22 +155,24 @@ export function LastAlertsStat({
}}
href={discoverLocation}
>
- {countLastAlerts ?? '-'}
+ {statValue}
}
- description={`${severity.label} severity`}
+ description={statDescription}
descriptionElement='h3'
titleColor={severity.color}
- textAlign='center'
+ textAlign={textAlign}
/>
-
- {'Rule level ' +
- ruleLevelRange.minRuleLevel +
- (ruleLevelRange.maxRuleLevel
- ? ' to ' + ruleLevelRange.maxRuleLevel
- : ' or higher')}
-
+ {hideBottomText ? null : (
+
+ {'Rule level ' +
+ ruleLevelRange.minRuleLevel +
+ (ruleLevelRange.maxRuleLevel
+ ? ' to ' + ruleLevelRange.maxRuleLevel
+ : ' or higher')}
+
+ )}
);
diff --git a/plugins/main/public/controllers/overview/components/stats.js b/plugins/main/public/controllers/overview/components/stats.js
index 97fbb7cfec..aec7fcbfba 100644
--- a/plugins/main/public/controllers/overview/components/stats.js
+++ b/plugins/main/public/controllers/overview/components/stats.js
@@ -79,20 +79,39 @@ export const Stats = withErrorBoundary(
);
}
+ /**
+ * Calculate the size of the visualization evaluating if it renders the internal loading or the chart
+ * based on the viewport size
+ */
+ getVisualizationSize() {
+ const normalLoadingSize = { width: 377, height: '150px' };
+ const mobileLoadingSize = {
+ height: '150px',
+ };
+ const loadingSize =
+ window.innerWidth < 768 ? mobileLoadingSize : normalLoadingSize;
+ const size = this.props.isAgentsLoading
+ ? loadingSize
+ : { width: '100%', height: '150px' };
+ return size;
+ }
+
render() {
+ const { isAgentsLoading } = this.props;
const hasResults = this.agentStatus.some(
({ status }) => this.props[status],
);
+ const showAgentsChart = isAgentsLoading || hasResults;
return (
- {hasResults ? (
+ {showAgentsChart ? (
({
@@ -107,36 +126,33 @@ export const Stats = withErrorBoundary(
)}
/>
) : (
- !hasResults &&
- this.props !== undefined && (
-
- This instance has no agents registered.
-
- Please deploy agents to begin monitoring your endpoints.
-
- }
- actions={
-
- Deploy new agent
-
- }
- />
- )
+
+ This instance has no agents registered.
+
+ Please deploy agents to begin monitoring your endpoints.
+
+ }
+ actions={
+
+ Deploy new agent
+
+ }
+ />
)}
diff --git a/plugins/main/public/kibana-integrations/discover/application/components/hits_counter/hits_counter.tsx b/plugins/main/public/kibana-integrations/discover/application/components/hits_counter/hits_counter.tsx
index a46e0d0e3c..061cf59ea7 100644
--- a/plugins/main/public/kibana-integrations/discover/application/components/hits_counter/hits_counter.tsx
+++ b/plugins/main/public/kibana-integrations/discover/application/components/hits_counter/hits_counter.tsx
@@ -17,7 +17,14 @@
* under the License.
*/
import React from 'react';
-import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, EuiIcon } from '@elastic/eui';
+import {
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ EuiToolTip,
+ EuiIcon,
+} from '@elastic/eui';
import { FormattedMessage, I18nProvider } from '@osd/i18n/react';
import { i18n } from '@osd/i18n';
import { formatNumWithCommas } from '../../helpers';
@@ -43,45 +50,62 @@ export interface HitsCounterProps {
};
}
-export function HitsCounter({ hits, showResetButton, onResetQuery, tooltip }: HitsCounterProps) {
+export function HitsCounter({
+ hits,
+ showResetButton,
+ onResetQuery,
+ tooltip,
+}: HitsCounterProps) {
return (
- {formatNumWithCommas(hits)} {' '}
+
+ {formatNumWithCommas(hits)}
+ {' '}
{' '}
- {tooltip && tooltip.content && (
-
- )}
+ {tooltip && tooltip.content && (
+
+
+
+ )}
{showResetButton && (
diff --git a/plugins/main/public/react-services/interfaces/api-response.interface.ts b/plugins/main/public/react-services/interfaces/api-response.interface.ts
index b11739fedb..9a44513e68 100644
--- a/plugins/main/public/react-services/interfaces/api-response.interface.ts
+++ b/plugins/main/public/react-services/interfaces/api-response.interface.ts
@@ -1,4 +1,5 @@
interface IData {
+ api_version: string;
affected_items: Array;
failed_items: Array;
total_affected_items: number;
diff --git a/plugins/main/public/react-services/reporting.js b/plugins/main/public/react-services/reporting.js
index e198b461cf..466f664131 100644
--- a/plugins/main/public/react-services/reporting.js
+++ b/plugins/main/public/react-services/reporting.js
@@ -32,6 +32,7 @@ import {
} from '../../../../src/plugins/data/common';
import { getForceNow } from '../components/common/search-bar/search-bar-service';
import NavigationService from './navigation-service';
+import { Agent } from '../components/endpoints-summary/types';
export class ReportingService {
constructor() {
@@ -137,7 +138,11 @@ export class ReportingService {
return store.getState().reportingReducers?.dataSourceSearchContext;
}
- async startVis2Png(tab, agents = false, searchContext = null) {
+ async startVis2Png(
+ tab,
+ /** @type {Agent['id'] | false} */ agents = false,
+ /** @type {any} */ searchContext = null,
+ ) {
try {
const dataSourceContext =
searchContext || (await this.getDataSourceSearchContext());
diff --git a/plugins/main/public/react-services/wz-agents.ts b/plugins/main/public/react-services/wz-agents.ts
index d2a6b8a37d..ed67f04182 100644
--- a/plugins/main/public/react-services/wz-agents.ts
+++ b/plugins/main/public/react-services/wz-agents.ts
@@ -9,14 +9,11 @@
*
* Find more information about this on the LICENSE file.
*/
-import { ErrorToastOptions } from 'opensearch_dashboards/public';
import { WAZUH_AGENTS_OS_TYPE } from '../../common/constants';
-import { getToasts } from '../kibana-services';
+import { Agent } from '../components/endpoints-summary/types';
import { UnsupportedComponents } from '../utils/components-os-support';
-import IApiResponse from './interfaces/api-response.interface';
-import { WzRequest } from './wz-request';
-export function getAgentOSType(agent) {
+export function getAgentOSType(agent?: Agent) {
if (agent?.os?.uname?.toLowerCase().includes(WAZUH_AGENTS_OS_TYPE.LINUX)) {
return WAZUH_AGENTS_OS_TYPE.LINUX;
} else if (agent?.os?.platform === WAZUH_AGENTS_OS_TYPE.WINDOWS) {
diff --git a/plugins/main/public/sections.ts b/plugins/main/public/sections.ts
new file mode 100644
index 0000000000..b7be8d0a5b
--- /dev/null
+++ b/plugins/main/public/sections.ts
@@ -0,0 +1,11 @@
+export enum SECTIONS {
+ AGENTS = 'agents',
+ AGENTS_PREVIEW = 'agents-preview',
+ HEALTH_CHECK = 'health-check',
+ MANAGER = 'manager',
+ OVERVIEW = 'overview',
+ SECURITY = 'security',
+ SETTINGS = 'settings',
+ WAZUH_DEV = 'wazuh-dev',
+ BLANK_SCREEN = 'blank-screen',
+}
diff --git a/plugins/main/public/styles/common.scss b/plugins/main/public/styles/common.scss
index b9e4429542..31007c0175 100644
--- a/plugins/main/public/styles/common.scss
+++ b/plugins/main/public/styles/common.scss
@@ -324,12 +324,6 @@ input[type='search'].euiFieldSearch {
box-shadow: none;
}
-:focus:not(.wz-button):not(.input-filter-box):not(.kuiLocalSearchInput):not(
- .euiTextArea
- ):not(.euiPanel.euiPopover__panel.euiPopover__panel-isOpen) {
- box-shadow: none !important;
-}
-
/* Custom colors styles */
.color-white {
color: white;
diff --git a/plugins/main/public/styles/theme/dark/index.dark.scss b/plugins/main/public/styles/theme/dark/index.dark.scss
index 421d51f17f..4a0734970c 100644
--- a/plugins/main/public/styles/theme/dark/index.dark.scss
+++ b/plugins/main/public/styles/theme/dark/index.dark.scss
@@ -364,17 +364,6 @@ md-divider {
background: #1d1e24 !important;
}
-.wz-welcome-page-agent-info {
- box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.3),
- 0 1px 5px -2px rgba(0, 0, 0, 0.3) !important;
- background: #1d1e24 !important;
-}
-
-.wz-welcome-page-agent-info .wz-welcome-page-agent-info-details {
- background: #1a1b20 !important;
- border-bottom: 1px solid #343741 !important;
-}
-
.details-row {
background: #16171c !important;
border-top: 1px solid #343741 !important;
@@ -415,8 +404,6 @@ svg .legend text {
/* welcome-agent */
.wz-welcome-page-agent-tabs {
- padding: 12px 16px 1px 10px;
- min-height: 54px;
border-bottom: 1px solid #343741;
background-color: #1d1e24;
}
diff --git a/plugins/main/public/utils/applications.ts b/plugins/main/public/utils/applications.ts
index 76ecbc060c..0351a414f6 100644
--- a/plugins/main/public/utils/applications.ts
+++ b/plugins/main/public/utils/applications.ts
@@ -24,8 +24,9 @@ Endpoint security: 200
Threat intelligence: 300
Security operations: 400
Cloud security: 500
-Server management: 600
-Dashboard management: 700
+Agents management: 600
+Server management: 700
+Dashboard management
Indexer management (added to Wazuh dashboard default categories): 9000
*/
@@ -54,53 +55,34 @@ export const overview = {
}`,
};
-export const fileIntegrityMonitoring = {
+export const configurationAssessment = {
category: 'wz-category-endpoint-security',
- id: 'file-integrity-monitoring',
- title: i18n.translate('wz-app-file-integrity-monitoring-title', {
- defaultMessage: 'File Integrity Monitoring',
+ id: 'configuration-assessment',
+ title: i18n.translate('wz-app-configuration-assessment-title', {
+ defaultMessage: 'Configuration Assessment',
}),
breadcrumbLabel: i18n.translate(
- 'wz-app-file-integrity-monitoring-breadcrumbLabel',
+ 'wz-app-configuration-assessment-breadcrumbLabel',
{
- defaultMessage: 'File Integrity Monitoring',
+ defaultMessage: 'Configuration Assessment',
},
),
- description: i18n.translate('wz-app-file-integrity-monitoring-description', {
+ description: i18n.translate('wz-app-configuration-assessment-description', {
defaultMessage:
- 'Alerts related to file changes, including permissions, content, ownership, and attributes.',
+ 'Scan your assets as part of a configuration assessment audit.',
}),
- euiIconType: 'sqlApp',
+ order: 200,
+ euiIconType: 'managementApp',
showInOverviewApp: true,
showInAgentMenu: true,
- order: 202,
redirectTo: () =>
- `/overview/?tab=fim&tabView=dashboard${
+ `/overview/?tab=sca&tabView=dashboard${
store.getState()?.appStateReducers?.currentAgentData?.id
? `&agentId=${store.getState()?.appStateReducers?.currentAgentData?.id}`
: ''
}`,
};
-export const endpointSummary = {
- category: 'wz-category-server-management',
- id: 'endpoints-summary',
- title: i18n.translate('wz-app-endpoints-summary-title', {
- defaultMessage: 'Endpoints Summary',
- }),
- breadcrumbLabel: i18n.translate('wz-app-endpoints-summary-breadcrumbLabel', {
- defaultMessage: 'Endpoints',
- }),
- description: i18n.translate('wz-app-endpoints-summary-description', {
- defaultMessage: 'Summary of agents and their status.',
- }),
- euiIconType: 'spacesApp',
- order: 600,
- showInOverviewApp: false,
- showInAgentMenu: false,
- redirectTo: () => '/agents-preview/',
-};
-
export const malwareDetection = {
category: 'wz-category-endpoint-security',
id: 'malware-detection',
@@ -126,28 +108,28 @@ export const malwareDetection = {
}`,
};
-export const configurationAssessment = {
+export const fileIntegrityMonitoring = {
category: 'wz-category-endpoint-security',
- id: 'configuration-assessment',
- title: i18n.translate('wz-app-configuration-assessment-title', {
- defaultMessage: 'Configuration Assessment',
+ id: 'file-integrity-monitoring',
+ title: i18n.translate('wz-app-file-integrity-monitoring-title', {
+ defaultMessage: 'File Integrity Monitoring',
}),
breadcrumbLabel: i18n.translate(
- 'wz-app-configuration-assessment-breadcrumbLabel',
+ 'wz-app-file-integrity-monitoring-breadcrumbLabel',
{
- defaultMessage: 'Configuration Assessment',
+ defaultMessage: 'File Integrity Monitoring',
},
),
- description: i18n.translate('wz-app-configuration-assessment-description', {
+ description: i18n.translate('wz-app-file-integrity-monitoring-description', {
defaultMessage:
- 'Scan your assets as part of a configuration assessment audit.',
+ 'Alerts related to file changes, including permissions, content, ownership, and attributes.',
}),
- order: 200,
- euiIconType: 'managementApp',
+ euiIconType: 'sqlApp',
showInOverviewApp: true,
showInAgentMenu: true,
+ order: 202,
redirectTo: () =>
- `/overview/?tab=sca&tabView=dashboard${
+ `/overview/?tab=fim&tabView=dashboard${
store.getState()?.appStateReducers?.currentAgentData?.id
? `&agentId=${store.getState()?.appStateReducers?.currentAgentData?.id}`
: ''
@@ -257,50 +239,50 @@ const pciDss = {
}`,
};
-const hipaa = {
+const gdpr = {
category: 'wz-category-security-operations',
- id: 'hipaa',
- title: i18n.translate('wz-app-hipaa-title', {
- defaultMessage: 'HIPAA',
+ id: 'gdpr',
+ title: i18n.translate('wz-app-gdpr-title', {
+ defaultMessage: 'GDPR',
}),
- breadcrumbLabel: i18n.translate('wz-app-hipaa-breadcrumbLabel', {
- defaultMessage: 'HIPAA',
+ breadcrumbLabel: i18n.translate('wz-app-gdpr-breadcrumbLabel', {
+ defaultMessage: 'GDPR',
}),
- description: i18n.translate('wz-app-hipaa-description', {
+ description: i18n.translate('wz-app-gdpr-description', {
defaultMessage:
- 'Health Insurance Portability and Accountability Act of 1996 (HIPAA) provides data privacy and security provisions for safeguarding medical information.',
+ 'General Data Protection Regulation (GDPR) sets guidelines for processing of personal data.',
}),
- euiIconType: 'monitoringApp',
- order: 402,
+ euiIconType: 'visBarVertical',
+ order: 401,
showInOverviewApp: true,
showInAgentMenu: true,
redirectTo: () =>
- `/overview/?tab=hipaa&tabView=dashboard${
+ `/overview/?tab=gdpr&tabView=dashboard${
store.getState()?.appStateReducers?.currentAgentData?.id
? `&agentId=${store.getState()?.appStateReducers?.currentAgentData?.id}`
: ''
}`,
};
-const gdpr = {
+const hipaa = {
category: 'wz-category-security-operations',
- id: 'gdpr',
- title: i18n.translate('wz-app-gdpr-title', {
- defaultMessage: 'GDPR',
+ id: 'hipaa',
+ title: i18n.translate('wz-app-hipaa-title', {
+ defaultMessage: 'HIPAA',
}),
- breadcrumbLabel: i18n.translate('wz-app-gdpr-breadcrumbLabel', {
- defaultMessage: 'GDPR',
+ breadcrumbLabel: i18n.translate('wz-app-hipaa-breadcrumbLabel', {
+ defaultMessage: 'HIPAA',
}),
- description: i18n.translate('wz-app-gdpr-description', {
+ description: i18n.translate('wz-app-hipaa-description', {
defaultMessage:
- 'General Data Protection Regulation (GDPR) sets guidelines for processing of personal data.',
+ 'Health Insurance Portability and Accountability Act of 1996 (HIPAA) provides data privacy and security provisions for safeguarding medical information.',
}),
- euiIconType: 'visBarVertical',
- order: 401,
+ euiIconType: 'monitoringApp',
+ order: 402,
showInOverviewApp: true,
showInAgentMenu: true,
redirectTo: () =>
- `/overview/?tab=gdpr&tabView=dashboard${
+ `/overview/?tab=hipaa&tabView=dashboard${
store.getState()?.appStateReducers?.currentAgentData?.id
? `&agentId=${store.getState()?.appStateReducers?.currentAgentData?.id}`
: ''
@@ -357,6 +339,31 @@ const tsc = {
}`,
};
+export const docker = {
+ category: 'wz-category-cloud-security',
+ id: 'docker',
+ title: i18n.translate('wz-app-docker-title', {
+ defaultMessage: 'Docker',
+ }),
+ breadcrumbLabel: i18n.translate('wz-app-docker-breadcrumbLabel', {
+ defaultMessage: 'Docker',
+ }),
+ description: i18n.translate('wz-app-docker-description', {
+ defaultMessage:
+ 'Monitor and collect the activity from Docker containers such as creation, running, starting, stopping or pausing events.',
+ }),
+ euiIconType: LogoDocker,
+ order: 500,
+ showInOverviewApp: true,
+ showInAgentMenu: true,
+ redirectTo: () =>
+ `/overview/?tab=docker&tabView=dashboard${
+ store.getState()?.appStateReducers?.currentAgentData?.id
+ ? `&agentId=${store.getState()?.appStateReducers?.currentAgentData?.id}`
+ : ''
+ }`,
+};
+
export const amazonWebServices = {
category: 'wz-category-cloud-security',
id: 'amazon-web-services',
@@ -374,7 +381,7 @@ export const amazonWebServices = {
'Security events related to your Amazon AWS services, collected directly via AWS API.',
}),
euiIconType: 'logoAWSMono',
- order: 500,
+ order: 501,
showInOverviewApp: true,
showInAgentMenu: true,
redirectTo: () =>
@@ -399,7 +406,7 @@ export const googleCloud = {
'Security events related to your Google Cloud Platform services, collected directly via GCP API.',
}),
euiIconType: LogoGoogleCloud,
- order: 501,
+ order: 502,
showInOverviewApp: true,
showInAgentMenu: true,
redirectTo: () =>
@@ -424,7 +431,7 @@ export const github = {
'Monitoring events from audit logs of your GitHub organizations.',
}),
euiIconType: LogoGitHub,
- order: 502,
+ order: 503,
showInOverviewApp: true,
showInAgentMenu: true,
redirectTo: () =>
@@ -448,7 +455,7 @@ export const office365 = {
defaultMessage: 'Security events related to your Office 365 services.',
}),
euiIconType: LogoOffice365,
- order: 503,
+ order: 504,
showInOverviewApp: true,
showInAgentMenu: true,
redirectTo: () =>
@@ -459,29 +466,42 @@ export const office365 = {
}`,
};
-export const docker = {
- category: 'wz-category-cloud-security',
- id: 'docker',
- title: i18n.translate('wz-app-docker-title', {
- defaultMessage: 'Docker',
+export const endpointSummary = {
+ category: 'wz-category-agents-management',
+ id: 'endpoints-summary',
+ title: i18n.translate('wz-app-endpoints-summary-title', {
+ defaultMessage: 'Summary',
}),
- breadcrumbLabel: i18n.translate('wz-app-docker-breadcrumbLabel', {
- defaultMessage: 'Docker',
+ breadcrumbLabel: i18n.translate('wz-app-endpoints-summary-breadcrumbLabel', {
+ defaultMessage: 'Endpoints',
}),
- description: i18n.translate('wz-app-docker-description', {
- defaultMessage:
- 'Monitor and collect the activity from Docker containers such as creation, running, starting, stopping or pausing events.',
+ description: i18n.translate('wz-app-endpoints-summary-description', {
+ defaultMessage: 'Summary of agents and their status.',
}),
- euiIconType: LogoDocker,
- order: 404,
- showInOverviewApp: true,
- showInAgentMenu: true,
- redirectTo: () =>
- `/overview/?tab=docker&tabView=dashboard${
- store.getState()?.appStateReducers?.currentAgentData?.id
- ? `&agentId=${store.getState()?.appStateReducers?.currentAgentData?.id}`
- : ''
- }`,
+ euiIconType: 'spacesApp',
+ order: 600,
+ showInOverviewApp: false,
+ showInAgentMenu: false,
+ redirectTo: () => '/agents-preview/',
+};
+
+export const endpointGroups = {
+ category: 'wz-category-agents-management',
+ id: 'endpoint-groups',
+ title: i18n.translate('wz-app-endpoint-groups-title', {
+ defaultMessage: 'Endpoint Groups',
+ }),
+ breadcrumbLabel: i18n.translate('wz-app-endpoint-groups-breadcrumbLabel', {
+ defaultMessage: 'Endpoint Groups',
+ }),
+ description: i18n.translate('wz-app-endpoint-groups-description', {
+ defaultMessage: 'Manage your agent groups.',
+ }),
+ euiIconType: 'usersRolesApp',
+ order: 601,
+ showInOverviewApp: false,
+ showInAgentMenu: false,
+ redirectTo: () => '/manager/?tab=groups',
};
export const rules = {
@@ -497,7 +517,7 @@ export const rules = {
defaultMessage: 'Manage your cluster rules.',
}),
euiIconType: 'indexRollupApp',
- order: 602,
+ order: 700,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=ruleset',
@@ -516,7 +536,7 @@ export const decoders = {
defaultMessage: 'Manage your cluster decoders.',
}),
euiIconType: 'indexRollupApp',
- order: 603,
+ order: 701,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=decoders',
@@ -535,31 +555,12 @@ export const cdbLists = {
defaultMessage: 'Manage your cluster CDB list.',
}),
euiIconType: 'indexRollupApp',
- order: 604,
+ order: 702,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=lists',
};
-export const endpointGroups = {
- category: 'wz-category-server-management',
- id: 'endpoint-groups',
- title: i18n.translate('wz-app-endpoint-groups-title', {
- defaultMessage: 'Endpoint Groups',
- }),
- breadcrumbLabel: i18n.translate('wz-app-endpoint-groups-breadcrumbLabel', {
- defaultMessage: 'Endpoint Groups',
- }),
- description: i18n.translate('wz-app-endpoint-groups-description', {
- defaultMessage: 'Manage your agent groups.',
- }),
- euiIconType: 'usersRolesApp',
- order: 601,
- showInOverviewApp: false,
- showInAgentMenu: false,
- redirectTo: () => '/manager/?tab=groups',
-};
-
export const serverStatus = {
category: 'wz-category-server-management',
id: 'server-status',
@@ -573,7 +574,7 @@ export const serverStatus = {
defaultMessage: 'Manage your cluster status.',
}),
euiIconType: 'indexRollupApp',
- order: 605,
+ order: 703,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=status',
@@ -592,7 +593,7 @@ export const cluster = {
defaultMessage: 'Manage your cluster.',
}),
euiIconType: 'indexRollupApp',
- order: 606,
+ order: 704,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=monitoring',
@@ -611,7 +612,7 @@ export const statistics = {
defaultMessage: 'Information about the enviroment.',
}),
euiIconType: 'indexRollupApp',
- order: 607,
+ order: 705,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=statistics',
@@ -630,31 +631,12 @@ export const logs = {
defaultMessage: 'Logs from your cluster.',
}),
euiIconType: 'indexRollupApp',
- order: 608,
+ order: 706,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=logs',
};
-export const reporting = {
- category: 'wz-category-dashboard-management',
- id: 'reporting',
- title: i18n.translate('wz-app-reporting-title', {
- defaultMessage: 'Reporting',
- }),
- breadcrumbLabel: i18n.translate('wz-app-reporting-breadcrumbLabel', {
- defaultMessage: 'Reporting',
- }),
- description: i18n.translate('wz-app-reporting-description', {
- defaultMessage: 'Check your stored reports.',
- }),
- euiIconType: 'indexRollupApp',
- order: 10002,
- showInOverviewApp: false,
- showInAgentMenu: false,
- redirectTo: () => '/manager/?tab=reporting',
-};
-
export const settings = {
category: 'wz-category-server-management',
id: 'dashboards-settings',
@@ -668,7 +650,7 @@ export const settings = {
defaultMessage: 'Manage your cluster configuration.',
}),
euiIconType: 'indexRollupApp',
- order: 609,
+ order: 707,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/manager/?tab=configuration',
@@ -687,7 +669,7 @@ export const devTools = {
defaultMessage: 'Test the API endpoints.',
}),
euiIconType: 'devToolsApp',
- order: 610,
+ order: 708,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/wazuh-dev?tab=devTools',
@@ -706,7 +688,7 @@ export const rulesetTest = {
defaultMessage: 'Check your ruleset testing logs.',
}),
euiIconType: 'visualizeApp',
- order: 611,
+ order: 709,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/wazuh-dev?tab=logtest',
@@ -726,31 +708,12 @@ export const security = {
'Manage permissions to system resources based on the roles and policies.',
}),
euiIconType: 'securityAnalyticsApp',
- order: 612,
+ order: 710,
showInOverviewApp: false,
showInAgentMenu: false,
redirectTo: () => '/security?tab=users',
};
-export const serverApis = {
- category: 'wz-category-dashboard-management',
- id: 'server-apis',
- title: i18n.translate('wz-app-server-apis-title', {
- defaultMessage: 'Server APIs',
- }),
- breadcrumbLabel: i18n.translate('wz-app-server-apis-breadcrumbLabel', {
- defaultMessage: 'Server APIs',
- }),
- description: i18n.translate('wz-app-server-apis-description', {
- defaultMessage: 'Manage and configure the API entries.',
- }),
- euiIconType: 'indexRollupApp',
- order: 10003,
- showInOverviewApp: false,
- showInAgentMenu: false,
- redirectTo: () => '/settings?tab=api',
-};
-
export const sampleData = {
category: 'management',
id: 'sample-data',
@@ -770,6 +733,44 @@ export const sampleData = {
redirectTo: () => '/settings?tab=sample_data',
};
+export const reporting = {
+ category: 'wz-category-dashboard-management',
+ id: 'reporting',
+ title: i18n.translate('wz-app-reporting-title', {
+ defaultMessage: 'Reporting',
+ }),
+ breadcrumbLabel: i18n.translate('wz-app-reporting-breadcrumbLabel', {
+ defaultMessage: 'Reporting',
+ }),
+ description: i18n.translate('wz-app-reporting-description', {
+ defaultMessage: 'Check your stored reports.',
+ }),
+ euiIconType: 'indexRollupApp',
+ order: 10002,
+ showInOverviewApp: false,
+ showInAgentMenu: false,
+ redirectTo: () => '/manager/?tab=reporting',
+};
+
+export const serverApis = {
+ category: 'wz-category-dashboard-management',
+ id: 'server-apis',
+ title: i18n.translate('wz-app-server-apis-title', {
+ defaultMessage: 'Server APIs',
+ }),
+ breadcrumbLabel: i18n.translate('wz-app-server-apis-breadcrumbLabel', {
+ defaultMessage: 'Server APIs',
+ }),
+ description: i18n.translate('wz-app-server-apis-description', {
+ defaultMessage: 'Manage and configure the API entries.',
+ }),
+ euiIconType: 'indexRollupApp',
+ order: 10003,
+ showInOverviewApp: false,
+ showInAgentMenu: false,
+ redirectTo: () => '/settings?tab=api',
+};
+
export const appSettings = {
category: 'wz-category-dashboard-management',
id: 'app-settings',
@@ -897,12 +898,20 @@ export const Categories = [
order: 500,
euiIconType: 'watchesApp',
},
+ {
+ id: 'wz-category-agents-management',
+ label: i18n.translate('wz-app-category-agents-management', {
+ defaultMessage: 'Agents management',
+ }),
+ order: 600,
+ euiIconType: 'graphApp',
+ },
{
id: 'wz-category-server-management',
label: i18n.translate('wz-app-category-server-management', {
defaultMessage: 'Server management',
}),
- order: 600,
+ order: 700,
euiIconType: 'indexRollupApp',
},
{
@@ -910,13 +919,13 @@ export const Categories = [
label: i18n.translate('wz-app-category-dashboard-management', {
defaultMessage: 'Dashboard management',
}),
- order: 6e3,
+ order: 5e3,
euiIconType: 'dashboardApp',
},
{
id: 'management',
label: 'Indexer management',
- order: 5e3,
+ order: 6e3,
euiIconType: 'managementApp',
},
];
diff --git a/plugins/main/test/public/query-attr.ts b/plugins/main/test/public/query-attr.ts
new file mode 100644
index 0000000000..8c790c5258
--- /dev/null
+++ b/plugins/main/test/public/query-attr.ts
@@ -0,0 +1,8 @@
+import { CSS } from '../utils/CSS';
+
+export function queryDataTestAttr(
+ attr: string,
+ selector: CSS.Selector = CSS.Attribute.Equals,
+) {
+ return `[data-test-subj${selector}="${attr}"]`;
+}
diff --git a/plugins/main/test/utils/CSS.ts b/plugins/main/test/utils/CSS.ts
new file mode 100644
index 0000000000..3c2109ece3
--- /dev/null
+++ b/plugins/main/test/utils/CSS.ts
@@ -0,0 +1,27 @@
+/*
+ [attribute] [lang] Selects all elements with lang attribute
+ [attribute=value] [lang="it"] Selects all elements with lang="it"
+ [attribute~=value] [title~="flower"] Selects all elements with a title attribute containing the word "flower"
+ [attribute|=value] [lang|="en"] Selects all elements with a lang attribute value equal to "en" or starting with "en-"
+ [attribute^=value] [href^="https"] Selects all elements with a href attribute value that begins with "https"
+ [attribute$=value] [href$=".pdf"] Selects all elements with a href attribute value ends with ".pdf"
+ [attribute*=value] [href*="w3schools"] Selects all elements with a href attribute value containing the substring "w3schools"
+ */
+export namespace CSS {
+ export enum Attribute {
+ Equals = '',
+ Includes = '~',
+ Dashes = '|',
+ Prefix = '^',
+ Suffix = '$',
+ Substring = '*',
+ }
+
+ export type Selector =
+ | Attribute.Equals
+ | Attribute.Includes
+ | Attribute.Dashes
+ | Attribute.Prefix
+ | Attribute.Suffix
+ | Attribute.Substring;
+}
diff --git a/scripts/release/bump.js b/scripts/release/bump.js
index 003cf78dbd..55e2afe035 100644
--- a/scripts/release/bump.js
+++ b/scripts/release/bump.js
@@ -25,6 +25,9 @@ const { updatePluginManifest } = require('./lib/update-manifest-plugin');
const { updateChangelog } = require('./lib/update-changelog');
const path = require('path');
const fs = require('fs');
+const {
+ updateImposterSpecificationReference,
+} = require('./lib/update-imposter');
const cli = require('../lib/cli/cli')(
cliName,
@@ -341,6 +344,8 @@ function run(configuration) {
},
logger,
);
+
+ updateImposterSpecificationReference(configuration, logger);
}
function main() {
diff --git a/scripts/release/lib/update-imposter.js b/scripts/release/lib/update-imposter.js
new file mode 100644
index 0000000000..e93caee867
--- /dev/null
+++ b/scripts/release/lib/update-imposter.js
@@ -0,0 +1,43 @@
+function updateImposterSpecificationReference(configuration, logger) {
+ try {
+ logger.debug('Editing imposter specification reference');
+ const fs = require('fs');
+ const path = require('path');
+ const specificationFile = path.join(
+ __dirname,
+ '../../..',
+ 'docker/imposter/wazuh-config.yml',
+ );
+
+ logger.debug(`Reading ${specificationFile} file`);
+ const content = fs.readFileSync(specificationFile, 'utf8');
+
+ const { version } = configuration;
+
+ if (!version) {
+ throw new Error('Version is not specified.');
+ }
+
+ // specFile: https://raw.githubusercontent.com/wazuh/wazuh//api/api/spec/spec.yaml
+ const updatedContent = content.replace(
+ /specFile:\s+\S+/m,
+ `specFile: https://raw.githubusercontent.com/wazuh/wazuh/${version}/api/api/spec/spec.yaml`,
+ );
+
+ if (content !== updatedContent) {
+ logger.debug(
+ `Updating [${specificationFile}] imposter specification file with latest changes`,
+ );
+ fs.writeFileSync(specificationFile, updatedContent, 'utf8');
+ logger.info(`${specificationFile} file has been updated`);
+ } else {
+ logger.debug(`Nothing to change in ${specificationFile} file`);
+ }
+ } catch (error) {
+ logger.error(`Error editing the specification file: ${error.message}`);
+ process.exit(1);
+ }
+}
+
+module.exports.updateImposterSpecificationReference =
+ updateImposterSpecificationReference;