diff --git a/public/components/common/buttons/modal-confirm.tsx b/public/components/common/buttons/modal-confirm.tsx
index aa809374ed..a92dc5d08b 100644
--- a/public/components/common/buttons/modal-confirm.tsx
+++ b/public/components/common/buttons/modal-confirm.tsx
@@ -35,12 +35,12 @@ interface WzButtonModalConfirmProps{
};
const renderModal = ({onConfirm, onCancel, modalTitle, modalConfirmText, modalCancelText, modalProps }) => ({close}) => {
- const onModalConfirm = () => {
- close();
+ const onModalConfirm = (ev) => {
+ close(ev);
onConfirm && onConfirm();
};
- const onModalCancel = () => {
- close();
+ const onModalCancel = (ev) => {
+ close(ev);
onCancel && onCancel();
};
return (
diff --git a/public/components/common/hocs/withButtonOpenOnClick.tsx b/public/components/common/hocs/withButtonOpenOnClick.tsx
index 63b9dc4908..f6303a64bc 100644
--- a/public/components/common/hocs/withButtonOpenOnClick.tsx
+++ b/public/components/common/hocs/withButtonOpenOnClick.tsx
@@ -12,11 +12,17 @@
import React, { useState } from 'react';
-export const withButtonOpenOnClick = WrappedComponent => ({render, ...rest} : {render?:any, [x:string]: any}) => {
+export const withButtonOpenOnClick = WrappedComponent => ({render, onClick, onClose, ...rest} : {render?:any, [x:string]: any}) => {
const [isOpen, setIsOpen] = useState(false);
- const open = ev => setIsOpen(true);
- const close = ev => setIsOpen(false);
+ const open = ev => {
+ typeof onClick === 'function' && onClick(ev);
+ setIsOpen(true);
+ };
+ const close = ev => {
+ typeof onClose === 'function' && onClose(ev);
+ setIsOpen(false)
+ };
return (
<>
diff --git a/public/components/common/tables/components/export-table-csv.tsx b/public/components/common/tables/components/export-table-csv.tsx
index b3c7c58eb7..01486f31e6 100644
--- a/public/components/common/tables/components/export-table-csv.tsx
+++ b/public/components/common/tables/components/export-table-csv.tsx
@@ -22,7 +22,7 @@ import { UI_ERROR_SEVERITIES } from '../../../../react-services/error-orchestrat
import { UI_LOGGER_LEVELS } from '../../../../../common/constants';
import { getErrorOrchestrator } from '../../../../react-services/common-services';
-export function ExportTableCsv({endpoint,totalItems,filters,title}){
+export function ExportTableCsv({ endpoint, totalItems, filters, title }) {
const showToast = (color, title, time) => {
getToasts().add({
@@ -42,7 +42,7 @@ export function ExportTableCsv({endpoint,totalItems,filters,title}){
[
...formatedFilters
],
- `vuls-${(title).toLowerCase()}`
+ `${(title).toLowerCase()}`
);
} catch (error) {
const options = {
diff --git a/public/components/common/tables/table-default.tsx b/public/components/common/tables/table-default.tsx
index 95aad1e4a4..23612c8a9f 100644
--- a/public/components/common/tables/table-default.tsx
+++ b/public/components/common/tables/table-default.tsx
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
import { EuiBasicTable } from '@elastic/eui';
import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types';
import { UI_LOGGER_LEVELS } from '../../../../common/constants';
@@ -43,6 +43,8 @@ export function TableDefault({
}
});
+ const isMounted = useRef(false);
+
function tableOnChange({ page = {}, sort = {} }){
const { index: pageIndex, size: pageSize } = page;
const { field, direction } = sort;
@@ -59,9 +61,14 @@ export function TableDefault({
};
useEffect(() => {
- // Reset the page index when the endpoint changes.
- // This will cause that onSearch function is triggered because to changes in pagination in the another effect.
+ // This effect is triggered when the component is mounted because of how to the useEffect hook works.
+ // We don't want to set the pagination state because there is another effect that has this dependency
+ // and will cause the effect is triggered (redoing the onSearch function).
+ if (isMounted.current) {
+ // Reset the page index when the endpoint changes.
+ // This will cause that onSearch function is triggered because to changes in pagination in the another effect.
setPagination({pageIndex: 0, pageSize: pagination.pageSize});
+ }
}, [endpoint]);
useEffect(() => {
@@ -90,6 +97,14 @@ export function TableDefault({
})()
}, [endpoint, pagination, sorting, reload]);
+
+ // It is required that this effect runs after other effects that use isMounted
+ // to avoid that these effects run when the component is mounted, only running
+ // when one of its dependencies changes.
+ useEffect(() => {
+ isMounted.current = true;
+ }, []);
+
const tablePagination = {
...pagination,
totalItemCount: totalItems,
diff --git a/public/components/common/tables/table-with-search-bar.tsx b/public/components/common/tables/table-with-search-bar.tsx
index 5bfd358049..6804dcb1a8 100644
--- a/public/components/common/tables/table-with-search-bar.tsx
+++ b/public/components/common/tables/table-with-search-bar.tsx
@@ -10,8 +10,9 @@
* Find more information about this on the LICENSE file.
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
import { EuiBasicTable, EuiSpacer } from '@elastic/eui';
+import _ from 'lodash';
import { WzSearchBar } from '../../wz-search-bar/';
import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types';
import { UI_LOGGER_LEVELS } from '../../../../common/constants';
@@ -48,29 +49,38 @@ export function TableWithSearchBar({
},
});
+ const isMounted = useRef(false);
+
function tableOnChange({ page = {}, sort = {} }) {
- const { index: pageIndex, size: pageSize } = page;
- const { field, direction } = sort;
- setPagination({
- pageIndex,
- pageSize,
- });
- setSorting({
- sort: {
- field,
- direction,
- },
- });
+ if (isMounted.current) {
+ const { index: pageIndex, size: pageSize } = page;
+ const { field, direction } = sort;
+ setPagination({
+ pageIndex,
+ pageSize,
+ });
+ setSorting({
+ sort: {
+ field,
+ direction,
+ },
+ });
+ }
}
useEffect(() => {
- // Reset the page index when the endpoint changes.
- // This will cause that onSearch function is triggered because to changes in pagination in the another effect.
- setPagination({pageIndex: 0, pageSize: pagination.pageSize});
- }, [endpoint]);
+ // This effect is triggered when the component is mounted because of how to the useEffect hook works.
+ // We don't want to set the pagination state because there is another effect that has this dependency
+ // and will cause the effect is triggered (redoing the onSearch function).
+ if (isMounted.current) {
+ // Reset the page index when the endpoint changes.
+ // This will cause that onSearch function is triggered because to changes in pagination in the another effect.
+ setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
+ }
+ }, [endpoint, reload]);
- useEffect(() => {
- (async function () {
+ useEffect(function () {
+ (async () => {
try {
setLoading(true);
const { items, totalItems } = await onSearch(endpoint, filters, pagination, sorting);
@@ -93,12 +103,24 @@ export function TableWithSearchBar({
}
setLoading(false);
})();
- }, [filters, pagination, sorting, reload]);
+ }, [filters, pagination, sorting]);
useEffect(() => {
- setFilters(rest.filters || []);
+ // This effect is triggered when the component is mounted because of how to the useEffect hook works.
+ // We don't want to set the filters state because there is another effect that has this dependency
+ // and will cause the effect is triggered (redoing the onSearch function).
+ if (isMounted.current && !_.isEqual(rest.filters, filters)) {
+ setFilters(rest.filters || []);
+ }
}, [rest.filters]);
+ // It is required that this effect runs after other effects that use isMounted
+ // to avoid that these effects run when the component is mounted, only running
+ // when one of its dependencies changes.
+ useEffect(() => {
+ isMounted.current = true;
+ }, []);
+
const tablePagination = {
...pagination,
totalItemCount: totalItems,
diff --git a/public/components/common/tables/table-wz-api.tsx b/public/components/common/tables/table-wz-api.tsx
index 35df090b93..f414e590bb 100644
--- a/public/components/common/tables/table-wz-api.tsx
+++ b/public/components/common/tables/table-wz-api.tsx
@@ -10,30 +10,59 @@
* Find more information about this on the LICENSE file.
*/
-import React, { useCallback, useState } from 'react';
+import React, { ReactNode, useCallback, useEffect, useState } from 'react';
import {
EuiTitle,
EuiLoadingSpinner,
EuiFlexGroup,
EuiFlexItem,
+ EuiText,
+ EuiButtonEmpty
} from '@elastic/eui';
import { filtersToObject } from '../../wz-search-bar';
import { TableWithSearchBar } from './table-with-search-bar';
import { TableDefault } from './table-default'
import { WzRequest } from '../../../react-services/wz-request';
-import { ExportTableCsv } from './components/export-table-csv';
+import { ExportTableCsv } from './components/export-table-csv';
import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types';
import { UI_LOGGER_LEVELS } from '../../../../common/constants';
import { getErrorOrchestrator } from '../../../react-services/common-services';
-export function TableWzAPI({...rest}){
+/**
+ * Search input custom filter button
+ */
+interface CustomFilterButton {
+ label: string
+ field: string
+ value: string
+}
+
+export function TableWzAPI({ actionButtons, ...rest }: {
+ actionButtons?: ReactNode | ReactNode[]
+ title?: string
+ description?: string
+ downloadCsv?: boolean
+ searchTable?: boolean
+ endpoint: string
+ buttonOptions?: CustomFilterButton[]
+ onFiltersChange?: Function,
+ showReload?: boolean
+ searchBarProps?: any
+ reload?: any
+}) {
const [totalItems, setTotalItems] = useState(0);
const [filters, setFilters] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const onFiltersChange = filters => typeof rest.onFiltersChange === 'function' ? rest.onFiltersChange(filters) : null;
- const onSearch = useCallback(async function(endpoint, filters, pagination, sorting){
+ /**
+ * Changing the reloadFootprint timestamp will trigger reloading the table
+ */
+ const [reloadFootprint, setReloadFootprint] = useState(rest.reload || 0);
+
+
+ const onSearch = useCallback(async function (endpoint, filters, pagination, sorting) {
try {
const { pageIndex, pageSize } = pagination;
const { field, direction } = sorting.sort;
@@ -68,36 +97,80 @@ export function TableWzAPI({...rest}){
};
getErrorOrchestrator().handleError(options);
};
- },[]);
+ }, []);
+
+ const renderActionButtons = (<>
+ {Array.isArray(actionButtons) ?
+ actionButtons.map((button, key) => {button}) :
+ (typeof actionButtons === 'object') && {actionButtons}}
+ >)
+
+ /**
+ * Generate a new reload footprint
+ */
+ const triggerReload = () => {
+ setReloadFootprint(Date.now());
+ }
+
+ useEffect(() => {
+ if (rest.reload)
+ triggerReload();
+ }, [rest.reload])
+
+ const ReloadButton = (
+
+ triggerReload()}>
+ Refresh
+
+
+ )
const header = (
-
-
+
+
{rest.title && (
- {rest.title} {isLoading ? : ({ totalItems })}
+ {rest.title} {isLoading ? : ({totalItems})}
)}
+ {rest.description && (
+
+ {rest.description}
+
+ )}
+
+
+
+ {/* Render optional custom action button */}
+ {renderActionButtons}
+ {/* Render optional reload button */}
+ {rest.showReload && ReloadButton}
+ {/* Render optional export to CSV button */}
+ {rest.downloadCsv && }
+
- {rest.downloadCsv && }
)
- const table = rest.searchTable ?
- :
-
-
+ const table = rest.searchTable ?
+ :
+
+
return (
- <>
- {header}
- {table}
- >)
+ <>
+ {header}
+ {table}
+ >)
}
// Set default props
diff --git a/public/components/common/welcome/components/menu-agent.js b/public/components/common/welcome/components/menu-agent.js
index 866e684a93..2cdeae3ae7 100644
--- a/public/components/common/welcome/components/menu-agent.js
+++ b/public/components/common/welcome/components/menu-agent.js
@@ -215,7 +215,6 @@ class WzMenuAgent extends Component {
const mapStateToProps = (state) => {
return {
- state: state.rulesetReducers,
currentAgentData: state.appStateReducers.currentAgentData,
currentTab: state.appStateReducers.currentTab,
};
diff --git a/public/components/wz-menu/wz-menu-agent.js b/public/components/wz-menu/wz-menu-agent.js
index d1ca750de0..5c54f3513e 100644
--- a/public/components/wz-menu/wz-menu-agent.js
+++ b/public/components/wz-menu/wz-menu-agent.js
@@ -277,7 +277,6 @@ class WzMenuAgent extends Component {
const mapStateToProps = state => {
return {
- state: state.rulesetReducers,
currentAgentData: state.appStateReducers.currentAgentData,
currentTab: state.appStateReducers.currentTab
};
diff --git a/public/components/wz-menu/wz-menu-management.js b/public/components/wz-menu/wz-menu-management.js
index 488baa66e1..615049ed16 100644
--- a/public/components/wz-menu/wz-menu-management.js
+++ b/public/components/wz-menu/wz-menu-management.js
@@ -191,7 +191,7 @@ class WzMenuManagement extends Component {
const mapStateToProps = state => {
return {
- state: state.rulesetReducers,
+ state: state.managementReducers,
};
};
diff --git a/public/components/wz-menu/wz-menu-overview.js b/public/components/wz-menu/wz-menu-overview.js
index 1e90d45b41..65ce09bf04 100644
--- a/public/components/wz-menu/wz-menu-overview.js
+++ b/public/components/wz-menu/wz-menu-overview.js
@@ -331,7 +331,6 @@ class WzMenuOverview extends Component {
const mapStateToProps = state => {
return {
- state: state.rulesetReducers,
currentAgentData: state.appStateReducers.currentAgentData,
currentTab: state.appStateReducers.currentTab
};
diff --git a/public/controllers/management/components/management/cdblists/components/cdblists-table.tsx b/public/controllers/management/components/management/cdblists/components/cdblists-table.tsx
new file mode 100644
index 0000000000..db27928072
--- /dev/null
+++ b/public/controllers/management/components/management/cdblists/components/cdblists-table.tsx
@@ -0,0 +1,191 @@
+/*
+ * Wazuh app - Agent vulnerabilities table component
+ * 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 } from 'react';
+import { TableWzAPI } from '../../../../../../components/common/tables';
+import { getToasts } from '../../../../../../kibana-services';
+import { resourceDictionary, ResourcesConstants, ResourcesHandler } from '../../common/resources-handler';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
+import { UI_ERROR_SEVERITIES } from '../../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../../common/constants';
+
+import { SECTION_CDBLIST_SECTION, SECTION_CDBLIST_KEY } from '../../common/constants';
+import CDBListsColumns from './columns';
+
+import { withUserPermissions } from '../../../../../../components/common/hocs/withUserPermissions';
+import { WzUserPermissions } from '../../../../../../react-services/wz-user-permissions';
+import { compose } from 'redux';
+import {
+ ManageFiles,
+ AddNewFileButton,
+ AddNewCdbListButton,
+ UploadFilesButton,
+} from '../../common/actions-buttons'
+
+function CDBListsTable(props) {
+ const [filters, setFilters] = useState([]);
+ const [showingFiles, setShowingFiles] = useState(false);
+ const [tableFootprint, setTableFootprint] = useState(0);
+
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.LISTS);
+
+ const updateFilters = (filters) => {
+ setFilters(filters);
+ }
+
+ const toggleShowFiles = () => {
+ setShowingFiles(!showingFiles);
+ }
+
+
+ const getColumns = () => {
+ const cdblistsColumns = new CDBListsColumns({
+ removeItems: removeItems,
+ state: {
+ section: SECTION_CDBLIST_KEY,
+ defaultItems: []
+ }, ...props
+ }).columns;
+ const columns = cdblistsColumns[SECTION_CDBLIST_KEY];
+ return columns;
+ }
+
+ /**
+ * Columns and Rows properties
+ */
+ const getRowProps = (item) => {
+ const { id, name } = item;
+
+ const getRequiredPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[SECTION_CDBLIST_KEY];
+ return [
+ {
+ action: `${SECTION_CDBLIST_KEY}:read`,
+ resource: permissionResource(item.name),
+ },
+ ];
+ };
+
+ return {
+ 'data-test-subj': `row-${id || name}`,
+ className: 'customRowClass',
+ onClick: !WzUserPermissions.checkMissingUserPermissions(
+ getRequiredPermissions(item),
+ props.userPermissions
+ )
+ ? async (ev) => {
+ const result = await resourcesHandler.getFileContent(item.filename);
+ const file = {
+ name: item.filename,
+ content: result,
+ path: item.relative_dirname,
+ };
+ updateListContent(file);
+ }
+ : undefined,
+ };
+ };
+
+ /**
+ * Remove files method
+ */
+ const removeItems = async (items) => {
+ try {
+ const results = items.map(async (item, i) => {
+ await resourcesHandler.deleteFile(item.filename || item.name);
+ });
+
+ Promise.all(results).then((completed) => {
+ setTableFootprint(Date.now());
+ getToasts().add({
+ color: 'success',
+ title: 'Success',
+ text: 'Deleted successfully',
+ toastLifeTimeMs: 3000,
+ });
+ });
+ } catch (error) {
+ const options = {
+ context: `${WzRulesetTable.name}.removeItems`,
+ level: UI_LOGGER_LEVELS.ERROR,
+ severity: UI_ERROR_SEVERITIES.BUSINESS,
+ error: {
+ error: error,
+ message: `Error deleting item: ${error.message || error}`,
+ title: error.name || error,
+ },
+ };
+ getErrorOrchestrator().handleError(options);
+ }
+ }
+
+ const { updateRestartClusterManager, updateListContent } = props;
+ const columns = getColumns();
+
+ /**
+ * Build table custom action buttons dynamically based on showing files state
+ */
+ const actionButtons = [
+ ,
+ ,
+ ,
+ { updateRestartClusterManager && updateRestartClusterManager() }}
+ />,
+ ];
+
+
+
+ return (
+
+ );
+
+}
+
+
+export default compose(
+ withUserPermissions
+)(CDBListsTable);
\ No newline at end of file
diff --git a/public/controllers/management/components/management/cdblists/components/columns.tsx b/public/controllers/management/components/management/cdblists/components/columns.tsx
new file mode 100644
index 0000000000..7afa10ea9d
--- /dev/null
+++ b/public/controllers/management/components/management/cdblists/components/columns.tsx
@@ -0,0 +1,185 @@
+import React from 'react';
+import { EuiToolTip, EuiButtonIcon, EuiBadge } from '@elastic/eui';
+import { resourceDictionary, ResourcesHandler, ResourcesConstants } from '../../common/resources-handler';
+import exportCsv from '../../../../../../react-services/wz-csv';
+import { WzButtonPermissions } from '../../../../../../components/common/permissions/button';
+import { WzButtonPermissionsModalConfirm } from '../../../../../../components/common/buttons';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
+import { UIErrorLog } from '../../../../../../react-services/error-orchestrator/types';
+import { getErrorOptions } from '../../common/error-helper';
+import { Columns } from '../../common/interfaces';
+
+export default class CDBListsColumns {
+
+ columns: Columns = { lists: [] };
+
+ constructor(props) {
+ this.props = props;
+ this._buildColumns();
+ }
+
+ _buildColumns() {
+ this.columns = {
+ lists: [
+ {
+ field: 'filename',
+ name: 'Name',
+ align: 'left',
+ sortable: true
+ },
+ {
+ field: 'relative_dirname',
+ name: 'Path',
+ align: 'left',
+ sortable: true
+ },
+ {
+ name: 'Actions',
+ align: 'left',
+ render: (item) => (
+
+ {
+ try {
+ ev.stopPropagation();
+ await exportCsv(`/lists?path=${item.relative_dirname}/${item.filename}`,
+ [{ _isCDBList: true, name: 'path', value: `${item.relative_dirname}/${item.filename}` }],
+ item.filename
+ )
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Lists.exportFile'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="primary"
+ />
+
+ )
+ }
+ ]
+ };
+
+ const getReadButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.LISTS];
+ return [
+ {
+ action: `${ResourcesConstants.LISTS}:read`,
+ resource: permissionResource(item.filename),
+ },
+ ];
+ };
+
+ const getEditButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.LISTS];
+ return [
+ {
+ action: `${ResourcesConstants.LISTS}:read`,
+ resource: permissionResource(item.filename),
+ },
+ { action: `${ResourcesConstants.LISTS}:update`, resource: permissionResource(item.filename) },
+ ];
+ };
+
+ const getDeleteButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.LISTS];
+ return [
+ {
+ action: `${ResourcesConstants.LISTS}:delete`,
+ resource: permissionResource(item.filename),
+ },
+ ];
+ };
+
+ // @ts-ignore
+ this.columns.lists[2] =
+ {
+ name: 'Actions',
+ align: 'left',
+ render: item => {
+ const defaultItems = this.props.state.defaultItems;
+ return (
+
+ {
+ try {
+ ev.stopPropagation();
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.LISTS);
+ const result = await resourcesHandler.getFileContent(item.filename);
+ const file = { name: item.filename, content: result, path: item.relative_dirname };
+ this.props.updateListContent(file);
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Lists.editFileContent'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="primary"
+ />
+ {
+ try {
+ this.props.removeItems([item])
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Lists.deleteFile'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ onClose={async (ev) => ev.stopPropagation()}
+ onClick={(ev) => ev.stopPropagation()}
+ color="danger"
+ modalTitle={'Are you sure?'}
+ modalProps={{
+ buttonColor: 'danger',
+ onClick: (ev) => ev.stopPropagation()
+ }}
+ />
+ {
+ try {
+ ev.stopPropagation();
+ await exportCsv(`/lists`,
+ [{ _isCDBList: true, name: 'filename', value: `${item.filename}` }],
+ item.filename
+ )
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Lists.exportFile'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="primary"
+ />
+
+ )
+ }
+ }
+ };
+}
diff --git a/public/controllers/management/components/management/cdblists/main-cdblists.tsx b/public/controllers/management/components/management/cdblists/main-cdblists.tsx
new file mode 100644
index 0000000000..1071e38602
--- /dev/null
+++ b/public/controllers/management/components/management/cdblists/main-cdblists.tsx
@@ -0,0 +1,40 @@
+/*
+ * Wazuh app - React component for main CDB List view.
+ * 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 } from 'react';
+// Redux
+import WzReduxProvider from '../../../../../redux/wz-redux-provider';
+import WzCDBListsOverview from './views/cdblists-overview';
+import WzListEditor from './views/list-editor';
+
+export default function WzCDBList({ clusterStatus }) {
+ const [listContent, setListContent] = useState(false);
+
+ return (
+
+ {
+ (listContent && (
+ { setListContent(false) }}
+ updateListContent={(listContent) => { setListContent(listContent) }}
+ />
+ )) || (
+ { setListContent(listContent) }}
+ />
+ )
+ }
+
+ );
+}
diff --git a/public/controllers/management/components/management/cdblists/views/cdblists-overview.tsx b/public/controllers/management/components/management/cdblists/views/cdblists-overview.tsx
new file mode 100644
index 0000000000..eb836acea4
--- /dev/null
+++ b/public/controllers/management/components/management/cdblists/views/cdblists-overview.tsx
@@ -0,0 +1,69 @@
+import React, { useState } from 'react';
+
+// Eui components
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPanel,
+ EuiPage,
+ EuiSpacer
+} from '@elastic/eui';
+
+// Wazuh components
+import { withUserAuthorizationPrompt, withGlobalBreadcrumb } from '../../../../../../components/common/hocs';
+import { compose } from 'redux';
+import { resourceDictionary } from '../../common/resources-handler';
+import { SECTION_CDBLIST_NAME, SECTION_CDBLIST_KEY } from '../../common/constants';
+import CDBListsTable from '../components/cdblists-table';
+import '../../common/layout-overview.scss';
+import WzRestartClusterManagerCallout from '../../../../../../components/common/restart-cluster-manager-callout';
+
+
+function WzCDBListsOverview(props) {
+
+ const [showWarningRestart, setShowWarningRestart] = useState(false);
+
+ const updateRestartManagers = (showWarningRestart) => {
+ setShowWarningRestart(showWarningRestart);
+ }
+
+ const { clusterStatus } = props;
+ return
+
+ {showWarningRestart && (
+ <>
+
+ updateRestartManagers(false)}
+ onRestartedError={() => updateRestartManagers(true)}
+ />
+
+ >
+ )}
+
+
+
+ updateRestartManagers(showWarningRestart)}
+ />
+
+
+
+ ;
+}
+
+
+export default compose(
+ withGlobalBreadcrumb(props => {
+ return [
+ { text: '' },
+ { text: 'Management', href: '#/manager' },
+ { text: SECTION_CDBLIST_NAME}
+ ];
+ }),
+ withUserAuthorizationPrompt((props) => [
+ { action: `${SECTION_CDBLIST_KEY}:read`, resource: resourceDictionary[SECTION_CDBLIST_KEY].permissionResource('*') }
+ ])
+)(WzCDBListsOverview);
diff --git a/public/controllers/management/components/management/ruleset/list-editor.js b/public/controllers/management/components/management/cdblists/views/list-editor.tsx
similarity index 89%
rename from public/controllers/management/components/management/ruleset/list-editor.js
rename to public/controllers/management/components/management/cdblists/views/list-editor.tsx
index b91b4e6997..88ba63855d 100644
--- a/public/controllers/management/components/management/ruleset/list-editor.js
+++ b/public/controllers/management/components/management/cdblists/views/list-editor.tsx
@@ -18,10 +18,8 @@ import {
EuiTitle,
EuiToolTip,
EuiButtonIcon,
- EuiButton,
EuiText,
EuiButtonEmpty,
- EuiPopover,
EuiFieldText,
EuiSpacer,
EuiPanel,
@@ -29,21 +27,20 @@ import {
import { connect } from 'react-redux';
-import { cleanInfo, updateListContent } from '../../../../../redux/actions/rulesetActions';
+import { resourceDictionary, ResourcesHandler, ResourcesConstants } from '../../common/resources-handler';
-import { resourceDictionary, RulesetHandler, RulesetResources } from './utils/ruleset-handler';
+import { getToasts } from '../../../../../../kibana-services';
-import { getToasts } from '../../../../../kibana-services';
+import exportCsv from '../../../../../../react-services/wz-csv';
-import exportCsv from '../../../../../react-services/wz-csv';
+import { updateWazuhNotReadyYet } from '../../../../../../redux/actions/appStateActions';
+import WzRestartClusterManagerCallout from '../../../../../../components/common/restart-cluster-manager-callout';
+import { WzButtonPermissions } from '../../../../../../components/common/permissions/button';
-import { updateWazuhNotReadyYet } from '../../../../../redux/actions/appStateActions';
-import WzRestartClusterManagerCallout from '../../../../../components/common/restart-cluster-manager-callout';
-import { WzButtonPermissions } from '../../../../../components/common/permissions/button';
+import { UI_ERROR_SEVERITIES } from '../../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../../common/constants';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
class WzListEditor extends Component {
constructor(props) {
super(props);
@@ -60,12 +57,11 @@ class WzListEditor extends Component {
};
this.items = {};
- this.rulesetHandler = new RulesetHandler(RulesetResources.LISTS);
+ this.resourcesHandler = new ResourcesHandler(ResourcesConstants.LISTS);
}
componentDidMount() {
- const { listInfo } = this.props.state;
- const { content } = listInfo;
+ const { listContent: { content } } = this.props;
const obj = this.contentToObject(content);
this.items = { ...obj };
const items = this.contentToArray(obj);
@@ -136,7 +132,7 @@ class WzListEditor extends Component {
return;
}
this.setState({ isSaving: true });
- await this.rulesetHandler.updateFile(name, raw, overwrite);
+ await this.resourcesHandler.updateFile(name, raw, overwrite);
if (!addingNew) {
const file = { name: name, content: raw, path: path };
this.props.updateListContent(file);
@@ -211,8 +207,8 @@ class WzListEditor extends Component {
getUpdatePermissions = (name) => {
return [
{
- action: `${RulesetResources.LISTS}:update`,
- resource: resourceDictionary[RulesetResources.LISTS].permissionResource(name),
+ action: `${ResourcesConstants.LISTS}:update`,
+ resource: resourceDictionary[ResourcesConstants.LISTS].permissionResource(name),
},
];
};
@@ -220,8 +216,8 @@ class WzListEditor extends Component {
getDeletePermissions = (name) => {
return [
{
- action: `${RulesetResources.LISTS}:delete`,
- resource: resourceDictionary[RulesetResources.LISTS].permissionResource(name),
+ action: `${ResourcesConstants.LISTS}:delete`,
+ resource: resourceDictionary[ResourcesConstants.LISTS].permissionResource(name),
},
];
};
@@ -292,7 +288,7 @@ class WzListEditor extends Component {
color="primary"
iconSize="l"
iconType="arrowLeft"
- onClick={() => this.props.cleanInfo()}
+ onClick={() => this.props.clearContent()}
/>
{name}
@@ -420,7 +416,7 @@ class WzListEditor extends Component {
color="primary"
iconSize="l"
iconType="arrowLeft"
- onClick={() => this.props.cleanInfo()}
+ onClick={() => this.props.clearContent()}
/>
{name}
@@ -525,13 +521,10 @@ class WzListEditor extends Component {
];
}
- //isDisabled={nameForSaving.length <= 4}
render() {
- const { listInfo, isLoading, error } = this.props.state;
- const { name, path } = listInfo;
+ const { listContent: { name, path }, isLoading } = this.props;
const message = isLoading ? false : 'No results...';
- const columns = this.columns;
const addingNew = name === false || !name;
const listName = this.state.newListName || name;
@@ -635,19 +628,11 @@ class WzListEditor extends Component {
}
}
-const mapStateToProps = (state) => {
- return {
- state: state.rulesetReducers,
- };
-};
-
const mapDispatchToProps = (dispatch) => {
return {
- cleanInfo: () => dispatch(cleanInfo()),
- updateListContent: (content) => dispatch(updateListContent(content)),
updateWazuhNotReadyYet: (wazuhNotReadyYet) =>
dispatch(updateWazuhNotReadyYet(wazuhNotReadyYet)),
};
};
-export default connect(mapStateToProps, mapDispatchToProps)(WzListEditor);
+export default connect(null, mapDispatchToProps)(WzListEditor);
diff --git a/public/controllers/management/components/management/common/actions-buttons.tsx b/public/controllers/management/components/management/common/actions-buttons.tsx
new file mode 100644
index 0000000000..8222516a6a
--- /dev/null
+++ b/public/controllers/management/components/management/common/actions-buttons.tsx
@@ -0,0 +1,187 @@
+/*
+ * Wazuh app - React component for tables custom action buttons.
+ * 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 from 'react';
+// Eui components
+
+import { UploadFiles } from '../../upload-files';
+import { resourceDictionary, ResourcesHandler } from './resources-handler';
+import { WzButtonPermissions } from '../../../../../components/common/permissions/button';
+
+import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
+import { getErrorOrchestrator } from '../../../../../react-services/common-services';
+import { SECTION_CDBLIST_SECTION } from './constants';
+
+/**
+ * Uploads the files
+ * @param {Array} files
+ * @param {String} path
+ */
+async function uploadFiles(files, resource, overwrite) {
+ try {
+ let errors = false;
+ let results: any[] = [];
+ const resourcesHandler = new ResourcesHandler(resource);
+ for (let idx in files) {
+ const { file, content } = files[idx];
+ try {
+ await resourcesHandler.updateFile(file, content, overwrite);
+ results.push({
+ index: idx,
+ uploaded: true,
+ file: file,
+ error: 0,
+ });
+ } catch (error) {
+ errors = true;
+ results.push({
+ index: idx,
+ uploaded: false,
+ file: file,
+ error: error,
+ });
+ }
+ }
+ if (errors) throw results;
+ return results;
+ } catch (error) {
+ throw error;
+ }
+}
+
+const getReadPermissionsFiles = (section) => {
+ const { permissionResource } = resourceDictionary[section];
+ return [
+ {
+ action: `${section}:read`,
+ resource: permissionResource('*'),
+ },
+ ];
+};
+
+const getUpdatePermissionsFiles = (section) => {
+ const { permissionResource } = resourceDictionary[section];
+ return [
+ {
+ action: `${section}:update`,
+ resource: permissionResource('*'),
+ },
+ {
+ action: `${section}:read`,
+ resource: permissionResource('*'),
+ },
+ ];
+};
+
+// Add new rule button
+export const AddNewFileButton = ({ section, updateAddingFile }) => (
+ <>{
+ section !== SECTION_CDBLIST_SECTION &&
+
+ updateAddingFile({
+ name: '',
+ content: '',
+ path: `etc/${section}`,
+ })
+ }
+ >
+ {`Add new ${section} file`}
+
+ }>
+)
+
+//Add new CDB list button
+export const AddNewCdbListButton = (({ section, updateListContent }) => {
+ return <>
+
+ updateListContent({
+ name: false,
+ content: '',
+ path: 'etc/lists',
+ })
+ }
+ >
+ {`Add new ${section} file`}
+
+ >
+});
+
+// Manage files
+export const ManageFiles = (({ section, showingFiles, ...props }) => {
+
+ /**
+ * Toggle between files and rules or decoders
+ */
+ const toggleFiles = async () => {
+ try {
+ props.toggleShowFiles(!showingFiles);
+ } catch (error) {
+ const options = {
+ context: 'ActionButtons.toggleFiles',
+ level: UI_LOGGER_LEVELS.ERROR,
+ severity: UI_ERROR_SEVERITIES.BUSINESS,
+ store: false,
+ error: {
+ error: error,
+ message: `Error toggling to files: ${error.message || error}`,
+ title: error.name || error,
+ },
+ };
+ getErrorOrchestrator().handleError(options);
+ }
+ }
+ return (
+ <>
+ {section !== SECTION_CDBLIST_SECTION &&
+ await toggleFiles()}
+ >
+ {showingFiles ? `Manage ${section}` : `Manage ${section} files`}
+ }
+ >
+ )
+});
+
+const uploadFile = async (files, resource, overwrite) => {
+ try {
+ const result = await uploadFiles(files, resource, overwrite);
+ return result;
+ } catch (error) {
+ return error;
+ }
+};
+
+export const UploadFilesButton = (({ clusterStatus, section, showingFiles, onSuccess, ...props }) => {
+
+ return (
+ {
+ onSuccess && onSuccess(true)
+ }}
+ />
+ )
+});
diff --git a/public/controllers/management/components/management/ruleset/utils/colors.js b/public/controllers/management/components/management/common/colors.ts
similarity index 100%
rename from public/controllers/management/components/management/ruleset/utils/colors.js
rename to public/controllers/management/components/management/common/colors.ts
diff --git a/public/controllers/management/components/management/common/constants.ts b/public/controllers/management/components/management/common/constants.ts
new file mode 100644
index 0000000000..4ad8214e6f
--- /dev/null
+++ b/public/controllers/management/components/management/common/constants.ts
@@ -0,0 +1,11 @@
+export const SECTION_RULES_NAME = 'Rules';
+export const SECTION_RULES_KEY = 'rules';
+export const SECTION_RULES_SECTION = 'rules';
+
+export const SECTION_DECODERS_NAME = 'Decoders';
+export const SECTION_DECODERS_KEY = 'decoders';
+export const SECTION_DECODERS_SECTION = 'decoders';
+
+export const SECTION_CDBLIST_NAME = 'CDB lists';
+export const SECTION_CDBLIST_KEY = 'lists';
+export const SECTION_CDBLIST_SECTION = 'lists';
diff --git a/public/controllers/management/components/management/common/error-helper.ts b/public/controllers/management/components/management/common/error-helper.ts
new file mode 100644
index 0000000000..813b792303
--- /dev/null
+++ b/public/controllers/management/components/management/common/error-helper.ts
@@ -0,0 +1,21 @@
+import { UIErrorLog, UILogLevel, UIErrorSeverity, UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
+/**
+ * Build and return a new error options object, based on the actual error
+ * and the context
+ * @param error raised error
+ * @param context context of the error
+ * @returns a dictionary with the error details for the ErrorOrchestator
+ */
+ export function getErrorOptions(error: unknown, context: string): UIErrorLog {
+ return {
+ context: context,
+ level: UI_LOGGER_LEVELS.ERROR as UILogLevel,
+ severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity,
+ error: {
+ error: error,
+ message: error?.message || error,
+ title: error?.name,
+ },
+ };
+ }
\ No newline at end of file
diff --git a/public/controllers/management/components/management/ruleset/ruleset-editor.js b/public/controllers/management/components/management/common/file-editor.tsx
similarity index 88%
rename from public/controllers/management/components/management/ruleset/ruleset-editor.js
rename to public/controllers/management/components/management/common/file-editor.tsx
index 73046b2d84..f21ce7f5f6 100644
--- a/public/controllers/management/components/management/ruleset/ruleset-editor.js
+++ b/public/controllers/management/components/management/common/file-editor.tsx
@@ -12,10 +12,6 @@
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
-import {
- cleanInfo,
- updateFileContent
-} from '../../../../../redux/actions/rulesetActions';
import 'brace/theme/textmate';
// Eui components
@@ -34,8 +30,8 @@ import {
EuiPanel,
} from '@elastic/eui';
-import { resourceDictionary, RulesetHandler, RulesetResources } from './utils/ruleset-handler';
-import validateConfigAfterSent from './utils/valid-configuration';
+import { resourceDictionary, ResourcesHandler } from './resources-handler';
+import validateConfigAfterSent from './valid-configuration';
import { getToasts } from '../../../../../kibana-services';
import { updateWazuhNotReadyYet } from '../../../../../redux/actions/appStateActions';
@@ -47,14 +43,13 @@ import 'brace/mode/xml';
import 'brace/snippets/xml';
import 'brace/ext/language_tools';
import "brace/ext/searchbox";
-import { showFlyoutLogtest } from '../../../../../redux/actions/appStateActions';
import _ from 'lodash';
import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-class WzRulesetEditor extends Component {
+class WzFileEditor extends Component {
_isMounted = false;
constructor(props) {
super(props);
@@ -68,9 +63,9 @@ class WzRulesetEditor extends Component {
enableSnippets: true,
enableLiveAutocompletion: false,
};
- this.rulesetHandler = new RulesetHandler(this.props.state.section);
- const { fileContent, addingRulesetFile } = this.props.state;
- const { name, content, path } = fileContent ? fileContent : addingRulesetFile;
+ this.resourcesHandler = new ResourcesHandler(this.props.section);
+ const { fileContent, addingFile } = this.props;
+ const { name, content, path } = fileContent ? fileContent : addingFile;
this.state = {
isSaving: false,
@@ -89,7 +84,7 @@ class WzRulesetEditor extends Component {
componentWillUnmount() {
// When the component is going to be unmounted its info is clear
this._isMounted = false;
- this.props.cleanInfo();
+ this.props.cleanEditState();
}
componentDidMount() {
@@ -106,7 +101,7 @@ class WzRulesetEditor extends Component {
if (!this._isMounted) {
return;
}else if(/\s/.test(name)) {
- this.showToast('warning', 'Warning', `The ${this.props.state.section} name must not contain spaces.`, 3000);
+ this.showToast('warning', 'Warning', `The ${this.props.section} name must not contain spaces.`, 3000);
return;
}
try {
@@ -114,12 +109,12 @@ class WzRulesetEditor extends Component {
this.setState({ isSaving: true, error: false });
- await this.rulesetHandler.updateFile(name, content, overwrite);
+ await this.resourcesHandler.updateFile(name, content, overwrite);
try {
await validateConfigAfterSent();
} catch (error) {
const options = {
- context: `${WzRulesetEditor.name}.save`,
+ context: `${WzFileEditor.name}.save`,
level: UI_LOGGER_LEVELS.ERROR,
severity: UI_ERROR_SEVERITIES.BUSINESS,
error: {
@@ -134,13 +129,13 @@ class WzRulesetEditor extends Component {
let toastMessage;
- if (this.props.state.addingRulesetFile != false) {
+ if (this.props.state.addingFile != false) {
//remove current invalid file if the file is new.
- await this.rulesetHandler.deleteFile(name);
+ await this.resourcesHandler.deleteFile(name);
toastMessage = 'The new file was deleted.';
} else {
//restore file to previous version
- await this.rulesetHandler.updateFile(name, this.state.initContent, overwrite);
+ await this.resourcesHandler.updateFile(name, this.state.initContent, overwrite);
toastMessage = 'The content file was restored to previous state.';
}
@@ -156,14 +151,20 @@ class WzRulesetEditor extends Component {
});
} catch (error) {
+ let errorMessage;
+ if (error instanceof Error) {
+ errorMessage = error.details
+ ? error.details
+ : String(error);
+ }
this.setState({ error, isSaving: false });
const options = {
- context: `${WzRulesetEditor.name}.save`,
+ context: `${WzFileEditor.name}.save`,
level: UI_LOGGER_LEVELS.ERROR,
severity: UI_ERROR_SEVERITIES.BUSINESS,
error: {
error: error,
- message: errorMessage,
+ message: errorMessage || error,
title: error.name || error,
},
};
@@ -196,15 +197,15 @@ class WzRulesetEditor extends Component {
};
render() {
- const { section, addingRulesetFile, fileContent } = this.props.state;
+ const { section, addingFile, fileContent } = this.props;
const { wazuhNotReadyYet } = this.props;
const { name, content, path, showWarningRestart } = this.state;
const isRules = path.includes('rules') ? 'Ruleset Test' : 'Decoders Test';
- const isEditable = addingRulesetFile
+ const isEditable = addingFile
? true
: path !== 'ruleset/rules' && path !== 'ruleset/decoders';
- let nameForSaving = addingRulesetFile ? this.state.inputValue : name;
+ let nameForSaving = addingFile ? this.state.inputValue : name;
nameForSaving = nameForSaving.endsWith('.xml') ? nameForSaving : `${nameForSaving}.xml`;
const overwrite = fileContent ? true : false;
@@ -212,7 +213,6 @@ class WzRulesetEditor extends Component {
const onClickOpenLogtest = () => {
this.props.logtestProps.openCloseFlyout();
- this.props.showFlyoutLogtest(true);
};
const buildLogtestButton = () => {
@@ -262,7 +262,7 @@ class WzRulesetEditor extends Component {
title="Unsubmitted changes"
onConfirm={() => {
closeModal;
- this.props.cleanInfo();
+ this.props.cleanEditState();
}}
onCancel={closeModal}
cancelButtonText="No, don't do it"
@@ -300,7 +300,7 @@ class WzRulesetEditor extends Component {
) {
showModal();
} else {
- this.props.cleanInfo();
+ this.props.cleanEditState();
}
}}
/>
@@ -332,7 +332,7 @@ class WzRulesetEditor extends Component {
) {
showModal();
} else {
- this.props.cleanInfo();
+ this.props.cleanEditState();
}
}}
/>
@@ -408,7 +408,6 @@ class WzRulesetEditor extends Component {
const mapStateToProps = state => {
return {
- state: state.rulesetReducers,
wazuhNotReadyYet: state.appStateReducers.wazuhNotReadyYet,
showFlyout: state.appStateReducers.showFlyoutLogtest,
};
@@ -416,14 +415,11 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
return {
- cleanInfo: () => dispatch(cleanInfo()),
- updateFileContent: content => dispatch(updateFileContent(content)),
updateWazuhNotReadyYet: wazuhNotReadyYet => dispatch(updateWazuhNotReadyYet(wazuhNotReadyYet)),
- showFlyoutLogtest: showFlyout => dispatch(showFlyoutLogtest(showFlyout)),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
-)(WzRulesetEditor);
+)(WzFileEditor);
diff --git a/public/controllers/management/components/management/common/flyout-detail.scss b/public/controllers/management/components/management/common/flyout-detail.scss
new file mode 100644
index 0000000000..197d3e62c4
--- /dev/null
+++ b/public/controllers/management/components/management/common/flyout-detail.scss
@@ -0,0 +1,17 @@
+.wz-decoders-flyout,
+.wz-ruleset-flyout {
+ .flyout-header {
+ padding-right: 42px;
+ }
+
+ .euiAccordion__button {
+ margin-top: 8px;
+ }
+
+ .euiFlyoutBody__overflow {
+ padding-top: 3px;
+ }
+ .euiAccordion__children-isLoading {
+ line-height: inherit;
+ }
+}
\ No newline at end of file
diff --git a/public/controllers/management/components/management/common/interfaces.ts b/public/controllers/management/components/management/common/interfaces.ts
new file mode 100644
index 0000000000..13b79041a9
--- /dev/null
+++ b/public/controllers/management/components/management/common/interfaces.ts
@@ -0,0 +1,15 @@
+export interface ColumnProps {
+ field?: string;
+ name?: string;
+ align?: string;
+ sortable?: boolean;
+ width?: string;
+ render?: Function | undefined
+}
+
+export interface Columns {
+ decoders?: Array;
+ rules?: Array;
+ lists?: Array;
+ files?: Array;
+}
\ No newline at end of file
diff --git a/public/controllers/management/components/management/ruleset/ruleset-overview.scss b/public/controllers/management/components/management/common/layout-overview.scss
similarity index 100%
rename from public/controllers/management/components/management/ruleset/ruleset-overview.scss
rename to public/controllers/management/components/management/common/layout-overview.scss
diff --git a/public/controllers/management/components/management/ruleset/utils/ruleset-handler.ts b/public/controllers/management/components/management/common/resources-handler.ts
similarity index 84%
rename from public/controllers/management/components/management/ruleset/utils/ruleset-handler.ts
rename to public/controllers/management/components/management/common/resources-handler.ts
index bff3abb517..293db983b8 100644
--- a/public/controllers/management/components/management/ruleset/utils/ruleset-handler.ts
+++ b/public/controllers/management/components/management/common/resources-handler.ts
@@ -1,31 +1,36 @@
-import { WzRequest } from '../../../../../../react-services';
+import { WzRequest } from '../../../../../react-services';
+import {
+ SECTION_DECODERS_SECTION,
+ SECTION_RULES_SECTION,
+ SECTION_CDBLIST_SECTION
+} from './constants';
type DECODERS = 'decoders';
type LISTS = 'lists';
type RULES = 'rules';
export type Resource = DECODERS | LISTS | RULES;
-export const RulesetResources = {
- DECODERS: 'decoders',
- LISTS: 'lists',
- RULES: 'rules',
+export const ResourcesConstants = {
+ DECODERS: SECTION_DECODERS_SECTION,
+ LISTS: SECTION_CDBLIST_SECTION,
+ RULES: SECTION_RULES_SECTION,
};
export const resourceDictionary = {
- [RulesetResources.DECODERS]: {
+ [ResourcesConstants.DECODERS]: {
resourcePath: '/decoders',
permissionResource: (value) => `decoder:file:${value}`
},
- [RulesetResources.LISTS]: {
+ [ResourcesConstants.LISTS]: {
resourcePath: '/lists',
permissionResource: (value) => `list:file:${value}`
},
- [RulesetResources.RULES]: {
+ [ResourcesConstants.RULES]: {
resourcePath: '/rules',
permissionResource: (value) => `rule:file:${value}`
},
};
-export class RulesetHandler {
+export class ResourcesHandler {
resource: Resource;
constructor(_resource: Resource) {
this.resource = _resource;
diff --git a/public/controllers/management/components/management/ruleset/utils/valid-configuration.js b/public/controllers/management/components/management/common/valid-configuration.ts
similarity index 95%
rename from public/controllers/management/components/management/ruleset/utils/valid-configuration.js
rename to public/controllers/management/components/management/common/valid-configuration.ts
index 41019120ed..9ffe4376da 100644
--- a/public/controllers/management/components/management/ruleset/utils/valid-configuration.js
+++ b/public/controllers/management/components/management/common/valid-configuration.ts
@@ -10,7 +10,7 @@
* Find more information about this on the LICENSE file.
*/
-import { WzRequest } from '../../../../../../react-services/wz-request';
+import { WzRequest } from '../../../../../react-services/wz-request';
const validateConfigAfterSent = async (node = false) => {
try {
diff --git a/public/controllers/management/components/management/decoders/components/columns.tsx b/public/controllers/management/components/management/decoders/components/columns.tsx
new file mode 100644
index 0000000000..9e3e160425
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/components/columns.tsx
@@ -0,0 +1,205 @@
+import React from 'react';
+import { resourceDictionary, ResourcesHandler, ResourcesConstants } from '../../common/resources-handler';
+import { WzButtonPermissions } from '../../../../../../components/common/permissions/button';
+import { WzButtonPermissionsModalConfirm } from '../../../../../../components/common/buttons';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
+import { UIErrorLog } from '../../../../../../react-services/error-orchestrator/types';
+import { getErrorOptions } from '../../common/error-helper';
+import { Columns } from '../../common/interfaces';
+
+
+export default class DecodersColumns {
+
+ columns: Columns = {};
+
+ constructor(props) {
+ this.props = props;
+ this._buildColumns();
+ }
+
+ _buildColumns() {
+ this.columns = {
+ decoders: [
+ {
+ field: 'name',
+ name: 'Name',
+ align: 'left',
+ sortable: true
+ },
+ {
+ field: 'details.program_name',
+ name: 'Program name',
+ align: 'left',
+ sortable: false
+ },
+ {
+ field: 'details.order',
+ name: 'Order',
+ align: 'left',
+ sortable: false
+ },
+ {
+ field: 'filename',
+ name: 'File',
+ align: 'left',
+ sortable: true,
+ render: (value, item) => {
+ return (
+ {
+ try {
+ ev.stopPropagation();
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.DECODERS);
+ const result = await resourcesHandler.getFileContent(value);
+ const file = { name: value, content: result, path: item.relative_dirname };
+ this.props.updateFileContent(file);
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Decoders.readFileContent'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}>
+ {value}
+
+ );
+ }
+ },
+ {
+ field: 'relative_dirname',
+ name: 'Path',
+ align: 'left',
+ sortable: true
+ }
+ ],
+ files: [
+ {
+ field: 'filename',
+ name: 'File',
+ align: 'left',
+ sortable: true
+ },
+ {
+ name: 'Actions',
+ align: 'left',
+ render: item => {
+ if (item.relative_dirname.startsWith('ruleset/')) {
+ return (
+ {
+ try {
+ ev.stopPropagation();
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.DECODERS);
+ const result = await resourcesHandler.getFileContent(item.filename);
+ const file = { name: item.filename, content: result, path: item.relative_dirname };
+ this.props.updateFileContent(file);
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Decoders.readFileContent'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="primary"
+ />
+ );
+ } else {
+ return (
+
+ {
+ try {
+ ev.stopPropagation();
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.DECODERS);
+ const result = await resourcesHandler.getFileContent(item.filename);
+ const file = { name: item.filename, content: result, path: item.relative_dirname };
+ this.props.updateFileContent(file);
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Files.editFileContent'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="primary"
+ />
+ {
+ try {
+ this.props.removeItems([item]);
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Files.deleteFile'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="danger"
+ modalTitle={'Are you sure?'}
+ modalProps={{
+ buttonColor: 'danger',
+ }}
+ />
+
+ );
+ }
+ }
+ }
+ ]
+ };
+
+ const getReadButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.DECODERS];
+ return [
+ {
+ action: `${ResourcesConstants.DECODERS}:read`,
+ resource: permissionResource(item.filename),
+ },
+ ];
+ };
+
+ const getEditButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.DECODERS];
+ return [
+ {
+ action: `${ResourcesConstants.DECODERS}:read`,
+ resource: permissionResource(item.filename),
+ },
+ { action: `${ResourcesConstants.DECODERS}:update`, resource: permissionResource(item.filename) },
+ ];
+ };
+
+ const getDeleteButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.DECODERS];
+ return [
+ {
+ action: `${ResourcesConstants.DECODERS}:delete`,
+ resource: permissionResource(item.filename),
+ },
+ ];
+ };
+
+ }
+}
diff --git a/public/controllers/management/components/management/decoders/components/decoders-suggestions.ts b/public/controllers/management/components/management/decoders/components/decoders-suggestions.ts
new file mode 100644
index 0000000000..fbd4ed6999
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/components/decoders-suggestions.ts
@@ -0,0 +1,44 @@
+import { WzRequest } from '../../../../../../react-services/wz-request';
+
+const decodersItems = [
+ {
+ type: 'params',
+ label: 'filename',
+ description: 'Filters the decoders by file name.',
+ values: async value => {
+ const filter = { limit: 30 };
+ if (value) {
+ filter['search'] = value;
+ }
+ const result = await WzRequest.apiReq('GET', '/decoders/files', filter);
+ return (((result || {}).data || {}).data || {}).affected_items.map((item) => { return item.filename });
+ },
+ },
+ {
+ type: 'params',
+ label: 'relative_dirname',
+ description: 'Path of the decoders files.',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/manager/configuration', {
+ params: {
+ section: 'ruleset',
+ field: 'decoder_dir'
+ }
+ });
+ return (((result || {}).data || {}).data || {}).affected_items[0].ruleset.decoder_dir;
+ }
+ },
+ {
+ type: 'params',
+ label: 'status',
+ description: 'Filters the decoders by status.',
+ values: ['enabled', 'disabled']
+ }
+];
+
+const apiSuggestsItems = {
+ items: decodersItems,
+ files: [],
+};
+
+export default apiSuggestsItems;
\ No newline at end of file
diff --git a/public/controllers/management/components/management/decoders/components/decoders-table.tsx b/public/controllers/management/components/management/decoders/components/decoders-table.tsx
new file mode 100644
index 0000000000..19e2357c64
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/components/decoders-table.tsx
@@ -0,0 +1,279 @@
+/*
+ * Wazuh app - Agent vulnerabilities table component
+ * 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, useCallback } from 'react';
+import { TableWzAPI } from '../../../../../../components/common/tables';
+import { getToasts } from '../../../../../../kibana-services';
+import { resourceDictionary, ResourcesConstants, ResourcesHandler } from '../../common/resources-handler';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
+import { UI_ERROR_SEVERITIES } from '../../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../../common/constants';
+
+import DecodersColumns from './columns';
+import { FlyoutDetail } from './flyout-detail';
+import { withUserPermissions } from '../../../../../../components/common/hocs/withUserPermissions';
+import { WzUserPermissions } from '../../../../../../react-services/wz-user-permissions';
+import { compose } from 'redux';
+import { SECTION_DECODERS_SECTION, SECTION_DECODERS_KEY } from '../../common/constants';
+import {
+ ManageFiles,
+ AddNewFileButton,
+ UploadFilesButton,
+} from '../../common/actions-buttons'
+
+import apiSuggestsItems from './decoders-suggestions';
+
+/***************************************
+ * Render tables
+ */
+const FilesTable = ({
+ actionButtons,
+ buttonOptions,
+ columns,
+ searchBarSuggestions,
+ filters,
+ updateFilters,
+ reload
+}) =>
+
+const DecodersFlyoutTable = ({
+ actionButtons,
+ buttonOptions,
+ columns,
+ searchBarSuggestions,
+ getRowProps,
+ filters,
+ updateFilters,
+ isFlyoutVisible,
+ currentItem,
+ closeFlyout,
+ cleanFilters,
+ ...props
+}) => <>
+
+ {isFlyoutVisible && (
+
+ )}
+ >
+
+/***************************************
+ * Main component
+ */
+export default compose(
+ withUserPermissions
+)(function DecodersTable({ setShowingFiles, showingFiles, ...props }) {
+ const [filters, setFilters] = useState([]);
+ const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const [currentItem, setCurrentItem] = useState(null);
+ const [tableFootprint, setTableFootprint] = useState(0);
+
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.DECODERS);
+
+ // Table custom filter options
+ const buttonOptions = [{ label: "Custom decoders", field: "relative_dirname", value: "etc/decoders" },];
+
+ const updateFilters = (filters) => {
+ setFilters(filters);
+ }
+
+ const cleanFilters = () => {
+ setFilters([]);
+ }
+
+ const toggleShowFiles = () => {
+ setFilters([]);
+ setShowingFiles(!showingFiles);
+ }
+
+ const closeFlyout = () => {
+ setIsFlyoutVisible(false);
+ setCurrentItem(null);
+ }
+
+ /**
+ * Columns and Rows properties
+ */
+ const getColumns = () => {
+ const decodersColumns = new DecodersColumns({
+ removeItems: removeItems,
+ state: {
+ section: SECTION_DECODERS_KEY
+ }, ...props
+ }).columns;
+ const columns = decodersColumns[showingFiles ? 'files' : SECTION_DECODERS_KEY];
+ return columns;
+ }
+
+ const getRowProps = (item) => {
+ const getRequiredPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[SECTION_DECODERS_KEY];
+ return [
+ {
+ action: `${SECTION_DECODERS_KEY}:read`,
+ resource: permissionResource(item.name),
+ },
+ ];
+ };
+
+ return {
+ 'data-test-subj': `row-${item.name}-${item.details?.order}`,
+ className: 'customRowClass',
+ onClick: !WzUserPermissions.checkMissingUserPermissions(
+ getRequiredPermissions(item),
+ props.userPermissions
+ )
+ ? () => {
+ setCurrentItem(item)
+ setIsFlyoutVisible(true);
+ }
+ : undefined,
+ };
+ };
+
+ /**
+ * Remove files method
+ */
+ const removeItems = async (items) => {
+ try {
+ const results = items.map(async (item, i) => {
+ await resourcesHandler.deleteFile(item.filename || item.name);
+ });
+
+ Promise.all(results).then((completed) => {
+ setTableFootprint(Date.now());
+ getToasts().add({
+ color: 'success',
+ title: 'Success',
+ text: 'Deleted successfully',
+ toastLifeTimeMs: 3000,
+ });
+ });
+ } catch (error) {
+ const options = {
+ context: `${WzRulesetTable.name}.removeItems`,
+ level: UI_LOGGER_LEVELS.ERROR,
+ severity: UI_ERROR_SEVERITIES.BUSINESS,
+ error: {
+ error: error,
+ message: `Error deleting item: ${error.message || error}`,
+ title: error.name || error,
+ },
+ };
+ getErrorOrchestrator().handleError(options);
+ }
+ }
+
+ const { updateRestartClusterManager, updateFileContent } = props;
+ const columns = getColumns();
+
+ /**
+ * Build table custom action buttons dynamically based on showing files state
+ */
+ const buildActionButtons = useCallback(() => {
+ const buttons = [
+ ,
+ ,
+ ];
+ if (showingFiles)
+ buttons.push( { updateRestartClusterManager && updateRestartClusterManager() }}
+ />);
+ return buttons;
+ }, [showingFiles]);
+
+ const actionButtons = buildActionButtons();
+
+ return (
+
+ {showingFiles ? (
+
+ ) : (
+
+ )}
+
+ );
+});
diff --git a/public/controllers/management/components/management/decoders/components/flyout-detail.tsx b/public/controllers/management/components/management/decoders/components/flyout-detail.tsx
new file mode 100644
index 0000000000..2295403cfc
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/components/flyout-detail.tsx
@@ -0,0 +1,36 @@
+/*
+ * Wazuh app - Agent vulnerabilities components
+ * 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 from 'react';
+import {
+ WzFlyout,
+} from '../../../../../../components/common/flyouts';
+import WzDecoderInfo from '../views/decoder-info';
+import '../../common/flyout-detail.scss'
+
+export const FlyoutDetail = ({ item, title, closeFlyout, filters, ...rest }) => {
+ return (
+ closeFlyout()}
+ flyoutProps={{
+ size: "l",
+ 'aria-labelledby': title,
+ maxWidth: "70%",
+ className: "wz-inventory wzApp wz-decoders-flyout",
+ }}
+ >
+ {item && <>
+
+ >}
+
+ );
+}
diff --git a/public/controllers/management/components/management/decoders/main-decoders.test.tsx b/public/controllers/management/components/management/decoders/main-decoders.test.tsx
new file mode 100644
index 0000000000..d6448ba063
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/main-decoders.test.tsx
@@ -0,0 +1,42 @@
+/*
+ * Wazuh app - React test for Ruleset component.
+ *
+ * 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 from 'react';
+import { mount } from 'enzyme';
+import WzRuleset from './main-ruleset';
+
+jest.mock('../../../../../kibana-services', () => ({
+ getAngularModule: jest.fn(),
+ getHttp: () => ({
+ basePath: {
+ prepend: (str) => str,
+ },
+ }),
+ getUiSettings: () => ({
+ get: (setting: string): any => {
+ if(setting === 'theme:darkMode'){
+ return false
+ }
+ }
+ })
+}));
+
+describe('Ruleset component', () => {
+ it('renders correctly to match the snapshot', () => {
+ const logtestProps = '';
+ const clusterStatus = '';
+ const wrapper = mount();
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/public/controllers/management/components/management/decoders/main-decoders.tsx b/public/controllers/management/components/management/decoders/main-decoders.tsx
new file mode 100644
index 0000000000..7948d2f959
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/main-decoders.tsx
@@ -0,0 +1,55 @@
+/*
+ * Wazuh app - React component for main Decoders view.
+ * 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 } from 'react';
+// Redux
+import WzReduxProvider from '../../../../../redux/wz-redux-provider';
+import WzDecodersOverview from './views/decoders-overview';
+import WzFileEditor from '../common/file-editor';
+import { SECTION_DECODERS_SECTION } from '../common/constants';
+
+export default function WzDecoder({ clusterStatus, logtestProps }) {
+
+ const [fileContent, setFileContent] = useState(false);
+ const [addingFile, setAddingFile] = useState(false);
+ const [showingFiles, setShowingFiles] = useState(false);
+
+ const cleanEditState = () => {
+ setFileContent(false);
+ setAddingFile(false);
+ }
+
+ return (
+
+ {
+ ((fileContent || addingFile) && (
+ { setFileContent(fileContent) }}
+ cleanEditState={() => cleanEditState()}
+ />
+ )) || (
+ { setFileContent(fileContent) }}
+ updateAddingFile={(addingFile) => { setAddingFile(addingFile) }}
+ setShowingFiles={() => { setShowingFiles(!showingFiles) }}
+ showingFiles={showingFiles}
+ />
+ )
+ }
+
+ );
+}
diff --git a/public/controllers/management/components/management/decoders/views/decoder-info.tsx b/public/controllers/management/components/management/decoders/views/decoder-info.tsx
new file mode 100644
index 0000000000..5e9446c259
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/views/decoder-info.tsx
@@ -0,0 +1,347 @@
+import React, { Component } from 'react';
+import _ from 'lodash';
+// Eui components
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiTitle,
+ EuiToolTip,
+ EuiSpacer,
+ EuiLink,
+ EuiAccordion,
+ EuiFlexGrid,
+} from '@elastic/eui';
+
+import { ResourcesHandler, ResourcesConstants } from '../../common/resources-handler';
+import { colors } from '../../common/colors';
+import { TableWzAPI } from '../../../../../../components/common/tables';
+import { UI_ERROR_SEVERITIES } from '../../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../../common/constants';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
+
+export default class WzDecoderInfo extends Component {
+ constructor(props) {
+ super(props);
+ this.onClickRow = this.onClickRow.bind(this);
+ this.state = {
+ currentInfo: {}
+ };
+
+ this.resourcesHandler = new ResourcesHandler(ResourcesConstants.DECODERS);
+
+ const handleFileClick = async (value, item) => {
+ try {
+ const result = await this.resourcesHandler.getFileContent(value);
+ const file = { name: value, content: result, path: item.relative_dirname };
+ this.props.updateFileContent(file);
+ } catch (error) {
+ const options = {
+ context: `${WzDecoderInfo.name}.handleFileClick`,
+ level: UI_LOGGER_LEVELS.ERROR,
+ severity: UI_ERROR_SEVERITIES.BUSINESS,
+ error: {
+ error: error,
+ message: `Error updating file content: ${error.message || error}`,
+ title: error.name || error,
+ },
+ };
+ getErrorOrchestrator().handleError(options);
+ }
+ };
+
+ this.columns = [
+ {
+ field: 'name',
+ name: 'Name',
+ align: 'left',
+ sortable: true,
+ },
+ {
+ field: 'details.program_name',
+ name: 'Program name',
+ align: 'left',
+ sortable: true,
+ },
+ {
+ field: 'details.order',
+ name: 'Order',
+ align: 'left',
+ sortable: true,
+ },
+ {
+ field: 'filename',
+ name: 'File',
+ align: 'left',
+ sortable: true,
+ render: (value, item) => {
+ return (
+
+ handleFileClick(value, item)}>{value}
+
+ );
+ },
+ },
+ {
+ field: 'relative_dirname',
+ name: 'Path',
+ align: 'left',
+ sortable: true,
+ },
+ ];
+ }
+
+ async componentDidMount() {
+ document.body.scrollTop = 0; // For Safari
+ document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
+
+ this.setState({
+ currentId: this.props.item
+ });
+ }
+
+ /**
+ * Clean the existing filters and sets the new ones and back to the previous section
+ */
+ setNewFiltersAndBack(filters) {
+ this.props.cleanFilters();
+ this.props.onFiltersChange(filters);
+ this.props.closeFlyout();
+ }
+
+ /**
+ * Render the basic information in a list
+ * @param {Number} position
+ * @param {String} file
+ * @param {String} path
+ */
+ renderInfo(position, file, path) {
+ return (
+
+
+ Position
+ {position}
+
+
+ File
+
+
+
+ this.setNewFiltersAndBack([{ field: 'filename', value: file }])
+ }
+ >
+ {file}
+
+
+
+
+
+ Path
+
+
+
+ this.setNewFiltersAndBack([{ field: 'relative_dirname', value: path }])
+ }
+ >
+ {path}
+
+
+
+
+
+
+ );
+ }
+
+ /**
+ * Render a list with the details
+ * @param {Array} details
+ */
+ renderDetails(details) {
+ const detailsToRender = [];
+ const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
+
+ Object.keys(details).forEach((key) => {
+ let content = details[key];
+ if (key === 'order') {
+ content = this.colorOrder(content);
+ } else if (typeof details[key] === 'object') {
+ content = (
+
+ {Object.keys(details[key]).map((k) => (
+ -
+ {k}:
+ {details[key][k]}
+
+
+ ))}
+
+ );
+ } else {
+ content = {details[key]};
+ }
+ detailsToRender.push(
+
+ {capitalize(key)}
+ {content}
+
+ );
+ });
+
+ return {detailsToRender};
+ }
+
+ /**
+ * This set a color to a given order
+ * @param {String} order
+ */
+ colorOrder(order) {
+ order = order.toString();
+ let valuesArray = order.split(',');
+ const result = [];
+ for (let i = 0, len = valuesArray.length; i < len; i++) {
+ const coloredString = (
+
+ {valuesArray[i].startsWith(' ') ? valuesArray[i] : ` ${valuesArray[i]}`}
+
+ );
+ result.push(coloredString);
+ }
+ return result;
+ }
+
+ /**
+ * This set a color to a given regex
+ * @param {String} regex
+ */
+ colorRegex(regex) {
+ regex = regex.toString();
+ const starts = (
+
+ {regex.split('(')[0]}
+
+ );
+ let valuesArray = regex.match(/\(((?!<\/span>).)*?\)(?!<\/span>)/gim);
+ const result = [starts];
+ for (let i = 0, len = valuesArray.length; i < len; i++) {
+ const coloredString = (
+
+ {valuesArray[i]}
+
+ );
+ result.push(coloredString);
+ }
+ return result;
+ }
+
+/**
+ * Update decoder details with the selected detail row
+ * @param decoder
+ */
+ onClickRow(decoder) {
+ return {
+ onClick: () => {
+ this.setState({ currentDecoder: decoder });
+ },
+ };
+ };
+
+ render() {
+ const currentDecoder =
+ this.state && this.state.currentDecoder ? this.state.currentDecoder : this.props.item;
+ const { position, details, filename, name, relative_dirname } = currentDecoder;
+
+ return (
+ <>
+
+ {/* Decoder description name */}
+
+
+
+
+ {name}
+
+
+
+
+
+
+ {/* Cards */}
+
+ {/* General info */}
+
+
+ Information
+
+ }
+ paddingSize="none"
+ initialIsOpen={true}
+ >
+
+ {this.renderInfo(position, filename, relative_dirname)}
+
+
+
+
+
+
+
+ Details
+
+ }
+ paddingSize="none"
+ initialIsOpen={true}
+ >
+ {this.renderDetails(details)}
+
+
+
+ {/* Table */}
+
+
+
+ Related decoders
+
+ }
+ paddingSize="none"
+ initialIsOpen={true}
+ >
+
+
+
+ {currentDecoder?.filename &&
+
+ }
+
+
+
+
+
+
+
+ >
+ );
+ }
+}
diff --git a/public/controllers/management/components/management/decoders/views/decoders-overview.tsx b/public/controllers/management/components/management/decoders/views/decoders-overview.tsx
new file mode 100644
index 0000000000..f47d4498dd
--- /dev/null
+++ b/public/controllers/management/components/management/decoders/views/decoders-overview.tsx
@@ -0,0 +1,71 @@
+import React, { useState } from 'react';
+import { connect } from 'react-redux';
+
+// Eui components
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPanel,
+ EuiPage,
+ EuiSpacer
+} from '@elastic/eui';
+
+// Wazuh components
+import { withUserAuthorizationPrompt, withGlobalBreadcrumb } from '../../../../../../components/common/hocs';
+import { compose } from 'redux';
+import { resourceDictionary } from '../../common/resources-handler';
+import { SECTION_DECODERS_NAME, SECTION_DECODERS_KEY } from '../../common/constants';
+import '../../common/layout-overview.scss';
+import DecodersTable from '../components/decoders-table';
+import WzRestartClusterManagerCallout from '../../../../../../components/common/restart-cluster-manager-callout';
+
+
+function WzDecodersOverview(props) {
+
+ const [showWarningRestart, setShowWarningRestart] = useState(false);
+
+ const updateRestartManagers = (showWarningRestart) => {
+ setShowWarningRestart(showWarningRestart);
+ }
+
+
+ const { clusterStatus } = props;
+ return
+
+ {showWarningRestart && (
+ <>
+
+ updateRestartManagers(false)}
+ onRestartedError={() => updateRestartManagers(true)}
+ />
+
+ >
+ )}
+
+
+
+ updateRestartManagers(showWarningRestart)}
+ />
+
+
+
+;
+
+}
+
+export default compose(
+ withGlobalBreadcrumb(props => {
+ return [
+ { text: '' },
+ { text: 'Management', href: '#/manager' },
+ { text: SECTION_DECODERS_NAME}
+ ];
+ }),
+ withUserAuthorizationPrompt((props) => [
+ { action: `${SECTION_DECODERS_KEY}:read`, resource: resourceDictionary[SECTION_DECODERS_KEY].permissionResource('*') }
+ ])
+)(WzDecodersOverview);
diff --git a/public/controllers/management/components/management/management-main.js b/public/controllers/management/components/management/management-main.js
index 74fbd84978..d940694b7b 100644
--- a/public/controllers/management/components/management/management-main.js
+++ b/public/controllers/management/components/management/management-main.js
@@ -13,8 +13,10 @@ import React, { Component, Fragment } from 'react';
// Redux
import store from '../../../../redux/store';
-import { updateRulesetSection } from '../../../../redux/actions/rulesetActions';
+import { updateManagementSection } from '../../../../redux/actions/managementActions';
import WzRuleset from './ruleset/main-ruleset';
+import WzCDBLists from './cdblists/main-cdblists';
+import WzDecoders from './decoders/main-decoders';
import WzGroups from './groups/groups-main';
import WzStatus from './status/status-main';
import WzLogs from './mg-logs/logs';
@@ -24,6 +26,11 @@ import WzStatistics from './statistics/statistics-main';
import { connect } from 'react-redux';
import { clusterReq } from './configuration/utils/wz-fetch';
import { updateClusterStatus } from '../../../../redux/actions/appStateActions';
+import {
+ SECTION_CDBLIST_SECTION,
+ SECTION_DECODERS_SECTION,
+ SECTION_RULES_SECTION
+} from './common/constants';
class WzManagementMain extends Component {
constructor(props) {
@@ -32,11 +39,11 @@ class WzManagementMain extends Component {
this.store = store;
}
UNSAFE_componentWillMount() {
- this.props.updateRulesetSection(this.props.section);
+ this.props.updateManagementSection(this.props.section);
}
componentWillUnmount() {
- store.dispatch(updateRulesetSection(''));
+ store.dispatch(updateManagementSection(''));
}
componentDidMount() {
@@ -68,7 +75,6 @@ class WzManagementMain extends Component {
render() {
const { section } = this.props;
- const ruleset = ['ruleset', 'rules', 'decoders', 'lists'];
return (
{(section === 'groups' && ) ||
@@ -77,7 +83,15 @@ class WzManagementMain extends Component {
(section === 'statistics' && ) ||
(section === 'logs' && ) ||
(section === 'configuration' && ) ||
- (ruleset.includes(section) && (
+ (section === SECTION_DECODERS_SECTION && ) ||
+ (section === SECTION_CDBLIST_SECTION && ) ||
+ (['ruleset', SECTION_RULES_SECTION].includes(section) && (
{
return {
- updateRulesetSection: (section) => dispatch(updateRulesetSection(section)),
+ updateManagementSection: (section) => dispatch(updateManagementSection(section)),
updateClusterStatus: (clusterStatus) => dispatch(updateClusterStatus(clusterStatus)),
};
};
diff --git a/public/controllers/management/components/management/ruleset/actions-buttons.js b/public/controllers/management/components/management/ruleset/actions-buttons.js
deleted file mode 100644
index 515496f6b9..0000000000
--- a/public/controllers/management/components/management/ruleset/actions-buttons.js
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component, Fragment } from 'react';
-// Eui components
-import { EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
-import { getToasts } from '../../../../../kibana-services';
-
-import {
- toggleShowFiles,
- updateLoadingStatus,
- updteAddingRulesetFile,
- updateListContent,
- updateIsProcessing,
- updatePageIndex,
-} from '../../../../../redux/actions/rulesetActions';
-
-import exportCsv from '../../../../../react-services/wz-csv';
-import { UploadFiles } from '../../upload-files';
-import columns from './utils/columns';
-import { resourceDictionary, RulesetHandler, RulesetResources } from './utils/ruleset-handler';
-import { WzButtonPermissions } from '../../../../../components/common/permissions/button';
-
-import { connect } from 'react-redux';
-
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-
-class WzRulesetActionButtons extends Component {
- constructor(props) {
- super(props);
-
- this.state = { generatingCsv: false };
- this.exportCsv = exportCsv;
-
- this.columns = columns;
- this.refreshTimeoutId = null;
- }
-
- showToast(title, text, color) {
- getToasts().add({
- title,
- text,
- color,
- toastLifeTimeMs: 3000,
- });
- }
- /**
- * Generates a CSV
- */
- async generateCsv() {
- try {
- this.setState({ generatingCsv: true });
- const { section, filters } = this.props.state; //TODO get filters from the search bar from the REDUX store
- const mapFilters = filters.map((filter) => ({
- name: filter.field,
- value: filter.value,
- })); // adapt to shape used in /api/csv file: server/controllers/wazuh-api.js
- await this.exportCsv(
- `/${section}${this.props.state.showingFiles ? '/files' : ''}`,
- mapFilters,
- section
- );
- } catch (error) {
- const options = {
- context: `${WzRulesetActionButtons.name}.generateCsv`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: `Error generating CSV: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
-
- this.setState({ generatingCsv: false });
- }
- this.setState({ generatingCsv: false });
- }
-
- /**
- * Uploads the files
- * @param {Array} files
- * @param {String} path
- */
- async uploadFiles(files, resource, overwrite) {
- try {
- let errors = false;
- let results = [];
- const rulesetHandler = new RulesetHandler(resource);
- for (let idx in files) {
- const { file, content } = files[idx];
- try {
- await rulesetHandler.updateFile(file, content, overwrite);
- results.push({
- index: idx,
- uploaded: true,
- file: file,
- error: 0,
- });
- } catch (error) {
- errors = true;
- results.push({
- index: idx,
- uploaded: false,
- file: file,
- error: error,
- });
- }
- }
- if (errors) throw results;
- return results;
- } catch (error) {
- throw error;
- }
- }
-
- /**
- * Toggle between files and rules or decoders
- */
- async toggleFiles() {
- try {
- this.props.updateLoadingStatus(true);
- const { showingFiles } = this.props.state;
- this.props.toggleShowFiles(!showingFiles);
- this.props.updateIsProcessing(true);
- this.props.updatePageIndex(0);
- this.props.updateLoadingStatus(false);
- } catch (error) {
- const options = {
- context: `${WzRulesetActionButtons.name}.toggleFiles`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: false,
- error: {
- error: error,
- message: `Error generating CSV: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- /**
- * Refresh the items
- */
- async refresh() {
- try {
- this.props.updateIsProcessing(true);
- // this.onRefreshLoading();
- } catch (error) {
- throw error;
- }
- }
-
- onRefreshLoading() {
- clearInterval(this.refreshTimeoutId);
-
- this.props.updateLoadingStatus(true);
- // this.refreshTimeoutId = setInterval(() => {
- // if(!this.props.state.isProcessing) {
- // clearInterval(this.refreshTimeoutId);
- // }
- // }, 100);
- }
-
- render() {
- const { section, showingFiles } = this.props.state;
-
- const getReadPermissionsFiles = () => {
- const { permissionResource } = resourceDictionary[section];
- return [
- {
- action: `${section}:read`,
- resource: permissionResource('*'),
- },
- ];
- };
-
- const getUpdatePermissionsFiles = () => {
- const { permissionResource } = resourceDictionary[section];
- return [
- {
- action: `${section}:update`,
- resource: permissionResource('*'),
- },
- {
- action: `${section}:read`,
- resource: permissionResource('*'),
- },
- ];
- };
-
- // Export button
- const exportButton = (
- await this.generateCsv()}
- isLoading={this.state.generatingCsv}
- >
- Export formatted
-
- );
-
- // Add new rule button
- const addNewRuleButton = (
-
- this.props.updteAddingRulesetFile({
- name: '',
- content: '',
- path: `etc/${section}`,
- })
- }
- >
- {`Add new ${section} file`}
-
- );
-
- //Add new CDB list button
- const addNewCdbListButton = (
-
- this.props.updateListContent({
- name: false,
- content: '',
- path: 'etc/lists',
- })
- }
- >
- {`Add new ${section} file`}
-
- );
-
- // Manage files
- const manageFiles = (
- await this.toggleFiles()}
- >
- {showingFiles ? `Manage ${section}` : `Manage ${section} files`}
-
- );
-
- // Refresh
- const refresh = (
- await this.refresh()}>
- Refresh
-
- );
-
- const uploadFile = async (files, resource, overwrite) => {
- try {
- const result = await this.uploadFiles(files, resource, overwrite);
- await this.refresh();
- return result
- } catch (error) {
- return error
- }
- };
-
- return (
-
- {section !== 'lists' && {manageFiles}}
- {section !== 'lists' && {addNewRuleButton}}
- {section === 'lists' && {addNewCdbListButton}}
- {(section === 'lists' || showingFiles) && (
-
- this.props.updateRestartClusterManager(true)}
- />
-
- )}
- {exportButton}
- {refresh}
-
- );
- }
-}
-
-const mapStateToProps = (state) => {
- return {
- state: state.rulesetReducers,
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- toggleShowFiles: (status) => dispatch(toggleShowFiles(status)),
- updateLoadingStatus: (status) => dispatch(updateLoadingStatus(status)),
- updteAddingRulesetFile: (content) => dispatch(updteAddingRulesetFile(content)),
- updateListContent: (content) => dispatch(updateListContent(content)),
- updateIsProcessing: (isProcessing) => dispatch(updateIsProcessing(isProcessing)),
- updatePageIndex: (pageIndex) => dispatch(updatePageIndex(pageIndex)),
- };
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(WzRulesetActionButtons);
diff --git a/public/controllers/management/components/management/ruleset/components/columns.tsx b/public/controllers/management/components/management/ruleset/components/columns.tsx
new file mode 100644
index 0000000000..ba57e1760f
--- /dev/null
+++ b/public/controllers/management/components/management/ruleset/components/columns.tsx
@@ -0,0 +1,281 @@
+import React from 'react';
+import { EuiToolTip, EuiBadge } from '@elastic/eui';
+import { resourceDictionary, ResourcesHandler, ResourcesConstants } from '../../common/resources-handler';
+import { WzButtonPermissions } from '../../../../../../components/common/permissions/button';
+import { WzButtonPermissionsModalConfirm } from '../../../../../../components/common/buttons';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
+import { UIErrorLog } from '../../../../../../react-services/error-orchestrator/types';
+import { getErrorOptions } from '../../common/error-helper';
+import { Columns } from '../../common/interfaces';
+
+
+export default class RulesetColumns {
+
+ columns: Columns = {};
+
+ constructor(props) {
+ this.props = props;
+ this._buildColumns();
+ }
+
+ _buildColumns() {
+ this.columns = {
+ rules: [
+ {
+ field: 'id',
+ name: 'ID',
+ align: 'left',
+ sortable: true,
+ width: '5%'
+ },
+ {
+ field: 'description',
+ name: 'Description',
+ align: 'left',
+ sortable: true,
+ width: '30%',
+ render: (value, item) => {
+ if (value === undefined) return '';
+ const regex = /\$(.*?)\)/g;
+ let result = value.match(regex);
+ let haveTooltip = false;
+ let toolTipDescription = false;
+ if (result !== null) {
+ haveTooltip = true;
+ toolTipDescription = value;
+ for (const oldValue of result) {
+ let newValue = oldValue.replace('$(', ``);
+ newValue = newValue.replace(')', ' ');
+ value = value.replace(oldValue, newValue);
+ }
+ }
+ return (
+
+ {haveTooltip === false ?
+ :
+
+
+
+ }
+
+ );
+ }
+ },
+ {
+ field: 'groups',
+ name: 'Groups',
+ align: 'left',
+ sortable: false,
+ width: '10%'
+ },
+ {
+ name: 'Regulatory compliance',
+ render: this.buildComplianceBadges
+ },
+ {
+ field: 'level',
+ name: 'Level',
+ align: 'left',
+ sortable: true,
+ width: '5%'
+ },
+ {
+ field: 'filename',
+ name: 'File',
+ align: 'left',
+ sortable: true,
+ width: '15%',
+ render: (value, item) => {
+ return (
+ {
+ try{
+ ev.stopPropagation();
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.RULES);
+ const result = await resourcesHandler.getFileContent(value);
+ const file = { name: value, content: result, path: item.relative_dirname };
+ this.props.updateFileContent(file);
+ }catch(error){
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Rules.readFileContent'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}>
+ {value}
+
+ );
+ }
+ },
+ {
+ field: 'relative_dirname',
+ name: 'Path',
+ align: 'left',
+ sortable: true,
+ width: '10%'
+ }
+ ],
+ files: [
+ {
+ field: 'filename',
+ name: 'File',
+ align: 'left',
+ sortable: true
+ },
+ {
+ name: 'Actions',
+ align: 'left',
+ render: item => {
+ if (item.relative_dirname.startsWith('ruleset/')) {
+ return (
+ {
+ try{
+ ev.stopPropagation();
+ const resourcesHandler = new ResourcesHandler(this.props.state.section);
+ const result = await resourcesHandler.getFileContent(item.filename);
+ const file = { name: item.filename, content: result, path: item.relative_dirname };
+ this.props.updateFileContent(file);
+ }catch(error){
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Files.readFileContent'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="primary"
+ />
+ );
+ } else {
+ return (
+
+ {
+ try {
+ ev.stopPropagation();
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.RULES);
+ const result = await resourcesHandler.getFileContent(item.filename);
+ const file = { name: item.filename, content: result, path: item.relative_dirname };
+ this.props.updateFileContent(file);
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Files.editFileContent'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="primary"
+ />
+ {
+ try {
+ this.props.removeItems([item]);
+ } catch (error) {
+ const options: UIErrorLog = getErrorOptions(
+ error,
+ 'Files.deleteFile'
+ );
+ getErrorOrchestrator().handleError(options);
+ }
+ }}
+ color="danger"
+ modalTitle={'Are you sure?'}
+ modalProps={{
+ buttonColor: 'danger',
+ }}
+ />
+
+ );
+ }
+ }
+ }
+ ]
+ };
+
+ const getReadButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.RULES];
+ return [
+ {
+ action: `${ResourcesConstants.RULES}:read`,
+ resource: permissionResource(item.filename),
+ },
+ ];
+ };
+
+ const getEditButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.RULES];
+ return [
+ {
+ action: `${ResourcesConstants.RULES}:read`,
+ resource: permissionResource(item.filename),
+ },
+ { action: `${ResourcesConstants.RULES}:update`, resource: permissionResource(item.filename) },
+ ];
+ };
+
+ const getDeleteButtonPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[ResourcesConstants.RULES];
+ return [
+ {
+ action: `${ResourcesConstants.RULES}:delete`,
+ resource: permissionResource(item.filename),
+ },
+ ];
+ };
+ }
+
+ buildComplianceBadges(item) {
+ const badgeList = [];
+ const fields = ['pci_dss', 'gpg13', 'hipaa', 'gdpr', 'nist_800_53', 'tsc', 'mitre'];
+ const buildBadge = field => {
+
+ return (
+
+ ev.stopPropagation()}
+ onClickAriaLabel={field.toUpperCase()}
+ style={{ margin: '1px 2px' }}
+ >
+ {field.toUpperCase()}
+
+
+ );
+ };
+ try {
+ for (const field of fields) {
+ if (item[field].length) {
+ badgeList.push(buildBadge(field));
+ }
+ }
+ } catch (error) { }
+
+ return {badgeList}
;
+ }
+}
diff --git a/public/controllers/management/components/management/ruleset/components/flyout-detail.tsx b/public/controllers/management/components/management/ruleset/components/flyout-detail.tsx
new file mode 100644
index 0000000000..d378b68733
--- /dev/null
+++ b/public/controllers/management/components/management/ruleset/components/flyout-detail.tsx
@@ -0,0 +1,38 @@
+/*
+ * Wazuh app - Rules components
+ * 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 from 'react';
+import {
+ WzFlyout,
+} from '../../../../../../components/common/flyouts';
+import WzRuleInfo from '../views/rule-info';
+import '../../common/flyout-detail.scss'
+
+export const FlyoutDetail = ({ isLoading, item, title, closeFlyout, filters, ...rest }) => {
+
+
+ return (
+ closeFlyout()}
+ flyoutProps={{
+ size: "l",
+ 'aria-labelledby': title,
+ maxWidth: "70%",
+ className: "wz-inventory wzApp wz-ruleset-flyout",
+ }}
+ >
+ {item && !isLoading && <>
+
+ >}
+
+ );
+}
diff --git a/public/controllers/management/components/management/ruleset/components/ruleset-suggestions.ts b/public/controllers/management/components/management/ruleset/components/ruleset-suggestions.ts
new file mode 100644
index 0000000000..84e4115185
--- /dev/null
+++ b/public/controllers/management/components/management/ruleset/components/ruleset-suggestions.ts
@@ -0,0 +1,141 @@
+import { WzRequest } from '../../../../../../react-services/wz-request';
+
+const rulesItems = [
+ {
+ type: 'params',
+ label: 'status',
+ description: 'Filters the rules by status.',
+ values: ['enabled', 'disabled']
+ },
+ {
+ type: 'params',
+ label: 'group',
+ description: 'Filters the rules by group',
+ values: async value => {
+ const filter = { limit: 30 };
+ if (value) {
+ filter['search'] = value;
+ }
+ const result = await WzRequest.apiReq('GET', '/rules/groups', filter);
+ return result?.data?.data?.affected_items;
+ },
+ },
+ {
+ type: 'params',
+ label: 'level',
+ description: 'Filters the rules by level',
+ values: [...Array(16).keys()]
+ },
+ {
+ type: 'params',
+ label: 'filename',
+ description: 'Filters the rules by file name.',
+ values: async value => {
+ const filter = { limit: 30 };
+ if (value) {
+ filter['search'] = value;
+ }
+ const result = await WzRequest.apiReq('GET', '/rules/files', filter);
+ return result?.data?.data?.affected_items?.map((item) => { return item.filename });
+ },
+ },
+ {
+ type: 'params',
+ label: 'relative_dirname',
+ description: 'Path of the rules files',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/manager/configuration', {
+ params: {
+ section: 'ruleset',
+ field: 'rule_dir'
+ }
+ });
+ return result?.data?.data?.affected_items?.[0].ruleset.rule_dir;
+ }
+ },
+ {
+ type: 'params',
+ label: 'hipaa',
+ description: 'Filters the rules by HIPAA requirement',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/rules/requirement/hipaa', {});
+ return result?.data?.data?.affected_items;
+ }
+ },
+ {
+ type: 'params',
+ label: 'gdpr',
+ description: 'Filters the rules by GDPR requirement',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/rules/requirement/gdpr', {});
+ return result?.data?.data?.affected_items;
+ }
+ },
+ {
+ type: 'params',
+ label: 'nist-800-53',
+ description: 'Filters the rules by NIST requirement',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/rules/requirement/nist-800-53', {});
+ return result?.data?.data?.affected_items;
+ }
+ },
+ {
+ type: 'params',
+ label: 'gpg13',
+ description: 'Filters the rules by GPG requirement',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/rules/requirement/gpg13', {});
+ return result?.data?.data?.affected_items;
+ }
+ },
+ {
+ type: 'params',
+ label: 'pci_dss',
+ description: 'Filters the rules by PCI DSS requirement',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/rules/requirement/pci_dss', {});
+ return result?.data?.data?.affected_items;
+ }
+ },
+ {
+ type: 'params',
+ label: 'tsc',
+ description: 'Filters the rules by TSC requirement',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/rules/requirement/tsc', {});
+ return result?.data?.data?.affected_items;
+ }
+ },
+ {
+ type: 'params',
+ label: 'mitre',
+ description: 'Filters the rules by MITRE requirement',
+ values: async () => {
+ const result = await WzRequest.apiReq('GET', '/rules/requirement/mitre', {});
+ return result?.data?.data?.affected_items;
+ }
+ }
+];
+const rulesFiles = [
+ {
+ type: 'params',
+ label: 'filename',
+ description: 'Filters the rules by file name.',
+ values: async value => {
+ const filter = { limit: 30 };
+ if (value) {
+ filter['search'] = value;
+ }
+ const result = await WzRequest.apiReq('GET', '/rules/files', filter);
+ return result?.data?.data?.affected_items?.map((item) => { return item.filename });
+ },
+ },
+];
+
+const apiSuggestsItems = {
+ items: rulesItems,
+ files: rulesFiles,
+};
+
+export default apiSuggestsItems;
diff --git a/public/controllers/management/components/management/ruleset/components/ruleset-table.tsx b/public/controllers/management/components/management/ruleset/components/ruleset-table.tsx
new file mode 100644
index 0000000000..ee5ff82bcd
--- /dev/null
+++ b/public/controllers/management/components/management/ruleset/components/ruleset-table.tsx
@@ -0,0 +1,295 @@
+/*
+ * Wazuh app - Agent ruleset table component
+ * 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, { useEffect, useState, useCallback } from 'react';
+import { getToasts } from '../../../../../../kibana-services';
+import { resourceDictionary, ResourcesConstants, ResourcesHandler } from '../../common/resources-handler';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
+import { UI_ERROR_SEVERITIES } from '../../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../../common/constants';
+
+import { TableWzAPI } from '../../../../../../components/common/tables';
+import { SECTION_RULES_SECTION, SECTION_RULES_KEY } from '../../common/constants';
+import RulesetColumns from './columns';
+import { FlyoutDetail } from './flyout-detail';
+import { withUserPermissions } from '../../../../../../components/common/hocs/withUserPermissions';
+import { WzUserPermissions } from '../../../../../../react-services/wz-user-permissions';
+import { compose } from 'redux';
+import {
+ ManageFiles,
+ AddNewFileButton,
+ UploadFilesButton,
+} from '../../common/actions-buttons'
+
+import apiSuggestsItems from './ruleset-suggestions';
+
+/***************************************
+ * Render tables
+ */
+const FilesTable = ({
+ actionButtons,
+ buttonOptions,
+ columns,
+ searchBarSuggestions,
+ filters,
+ updateFilters,
+ reload
+}) =>
+
+const RulesFlyoutTable = ({
+ actionButtons,
+ buttonOptions,
+ columns,
+ searchBarSuggestions,
+ filters,
+ updateFilters,
+ getRowProps,
+ isFlyoutVisible,
+ currentItem,
+ closeFlyout,
+ cleanFilters,
+ ...props
+}) => <>
+
+ {isFlyoutVisible && (
+
+ )}
+ >
+
+/***************************************
+ * Main component
+ */
+function RulesetTable({ setShowingFiles, showingFiles, ...props }) {
+ const [filters, setFilters] = useState([]);
+ const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+ const [currentItem, setCurrentItem] = useState(null);
+
+ const [tableFootprint, setTableFootprint] = useState(0);
+
+ const resourcesHandler = new ResourcesHandler(ResourcesConstants.RULES);
+
+ useEffect(() => {
+ const regex = new RegExp('redirectRule=' + '[^&]*');
+ const match = window.location.href.match(regex);
+ if (match && match[0]) {
+ setCurrentItem(parseInt(match[0].split('=')[1]))
+ setIsFlyoutVisible(true)
+ }
+ }, [])
+
+ // Table custom filter options
+ const buttonOptions = [{ label: "Custom rules", field: "relative_dirname", value: "etc/rules" },];
+
+ const updateFilters = (filters) => {
+ setFilters(filters);
+ }
+
+ const cleanFilters = () => {
+ setFilters([]);
+ }
+
+ const toggleShowFiles = () => {
+ setFilters([]);
+ setShowingFiles(!showingFiles);
+ }
+
+ const closeFlyout = () => {
+ setIsFlyoutVisible(false);
+ }
+
+
+ /**
+ * Remove files method
+ */
+ const removeItems = async (items) => {
+ try {
+ const results = items.map(async (item, i) => {
+ await resourcesHandler.deleteFile(item.filename || item.name);
+ });
+
+ Promise.all(results).then((completed) => {
+ setTableFootprint(Date.now());
+ getToasts().add({
+ color: 'success',
+ title: 'Success',
+ text: 'Deleted successfully',
+ toastLifeTimeMs: 3000,
+ });
+ });
+ } catch (error) {
+ const options = {
+ context: `${WzRulesetTable.name}.removeItems`,
+ level: UI_LOGGER_LEVELS.ERROR,
+ severity: UI_ERROR_SEVERITIES.BUSINESS,
+ error: {
+ error: error,
+ message: `Error deleting item: ${error.message || error}`,
+ title: error.name || error,
+ },
+ };
+ getErrorOrchestrator().handleError(options);
+ }
+ }
+
+ /**
+ * Columns and Rows properties
+ */
+ const getColumns = () => {
+ const rulesetColumns = new RulesetColumns({
+ removeItems: removeItems,
+ state: {
+ section: SECTION_RULES_KEY
+ }, ...props
+ }).columns;
+ const columns = rulesetColumns[showingFiles ? 'files' : SECTION_RULES_KEY];
+ return columns;
+ }
+
+ const getRowProps = (item) => {
+ const { id, name } = item;
+
+ const getRequiredPermissions = (item) => {
+ const { permissionResource } = resourceDictionary[SECTION_RULES_KEY];
+ return [
+ {
+ action: `${SECTION_RULES_KEY}:read`,
+ resource: permissionResource(item.name),
+ },
+ ];
+ };
+
+ return {
+ 'data-test-subj': `row-${id || name}`,
+ className: 'customRowClass',
+ onClick: !WzUserPermissions.checkMissingUserPermissions(
+ getRequiredPermissions(item),
+ props.userPermissions
+ )
+ ? (item) => {
+ setCurrentItem(id)
+ setIsFlyoutVisible(true);
+ }
+ : undefined,
+ };
+ };
+
+ const { updateRestartClusterManager, updateFileContent } = props;
+ const columns = getColumns();
+
+ /**
+ * Build table custom action buttons dynamically based on showing files state
+ */
+ const buildActionButtons = useCallback(() => {
+ const buttons = [
+ ,
+ ,
+ ];
+ if (showingFiles)
+ buttons.push( { updateRestartClusterManager && updateRestartClusterManager() }}
+ />);
+ return buttons;
+ }, [showingFiles]);
+
+ const actionButtons = buildActionButtons();
+
+ return (
+
+ {showingFiles ? (
+
+ ) : (
+
+ )}
+
+ );
+
+}
+
+
+export default compose(
+ withUserPermissions
+)(RulesetTable);
\ No newline at end of file
diff --git a/public/controllers/management/components/management/ruleset/decoder-info.js b/public/controllers/management/components/management/ruleset/decoder-info.js
deleted file mode 100644
index 6756f7222d..0000000000
--- a/public/controllers/management/components/management/ruleset/decoder-info.js
+++ /dev/null
@@ -1,393 +0,0 @@
-import React, { Component, Fragment } from 'react';
-// Eui components
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiPanel,
- EuiPage,
- EuiButtonIcon,
- EuiTitle,
- EuiToolTip,
- EuiText,
- EuiSpacer,
- EuiInMemoryTable,
- EuiLink,
- EuiAccordion,
- EuiFlexGrid,
-} from '@elastic/eui';
-
-import { connect } from 'react-redux';
-
-import { RulesetHandler, RulesetResources } from './utils/ruleset-handler';
-import { colors } from './utils/colors';
-
-import {
- updateFileContent,
- cleanFileContent,
- cleanInfo,
- updateFilters,
- cleanFilters,
-} from '../../../../../redux/actions/rulesetActions';
-
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-
-class WzDecoderInfo extends Component {
- constructor(props) {
- super(props);
-
- this.rulesetHandler = new RulesetHandler(RulesetResources.DECODERS);
-
- const handleFileClick = async (value, item) => {
- try {
- const result = await this.rulesetHandler.getFileContent(value);
- const file = { name: value, content: result, path: item.relative_dirname };
- this.props.updateFileContent(file);
- } catch (error) {
- const options = {
- context: `${WzDecoderInfo.name}.handleFileClick`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: `Error updating file content: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- };
-
- this.columns = [
- {
- field: 'name',
- name: 'Name',
- align: 'left',
- sortable: true,
- },
- {
- field: 'details.program_name',
- name: 'Program name',
- align: 'left',
- sortable: true,
- },
- {
- field: 'details.order',
- name: 'Order',
- align: 'left',
- sortable: true,
- },
- {
- field: 'filename',
- name: 'File',
- align: 'left',
- sortable: true,
- render: (value, item) => {
- return (
-
- handleFileClick(value, item)}>{value}
-
- );
- },
- },
- {
- field: 'relative_dirname',
- name: 'Path',
- align: 'left',
- sortable: true,
- },
- ];
- }
-
- /**
- * Clean the existing filters and sets the new ones and back to the previous section
- */
- setNewFiltersAndBack(filters) {
- this.props.cleanFilters();
- this.props.updateFilters(filters);
- this.props.cleanInfo();
- }
-
- /**
- * Render the basic information in a list
- * @param {Number} position
- * @param {String} file
- * @param {String} path
- */
- renderInfo(position, file, path) {
- return (
-
-
- Position
- {position}
-
-
- File
-
-
-
- this.setNewFiltersAndBack([{ field: 'filename', value: file }])
- }
- >
- {file}
-
-
-
-
-
- Path
-
-
-
- this.setNewFiltersAndBack([{ field: 'relative_dirname', value: path }])
- }
- >
- {path}
-
-
-
-
-
-
- );
- }
-
- /**
- * Render a list with the details
- * @param {Array} details
- */
- renderDetails(details) {
- const detailsToRender = [];
- const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
-
- Object.keys(details).forEach((key) => {
- let content = details[key];
- if (key === 'order') {
- content = this.colorOrder(content);
- } else if (typeof details[key] === 'object') {
- content = (
-
- {Object.keys(details[key]).map((k) => (
- -
- {k}:
- {details[key][k]}
-
-
- ))}
-
- );
- } else {
- content = {details[key]};
- }
- detailsToRender.push(
-
- {capitalize(key)}
- {content}
-
- );
- });
-
- return {detailsToRender};
- }
-
- /**
- * This set a color to a given order
- * @param {String} order
- */
- colorOrder(order) {
- order = order.toString();
- let valuesArray = order.split(',');
- const result = [];
- for (let i = 0, len = valuesArray.length; i < len; i++) {
- const coloredString = (
-
- {valuesArray[i].startsWith(' ') ? valuesArray[i] : ` ${valuesArray[i]}`}
-
- );
- result.push(coloredString);
- }
- return result;
- }
-
- /**
- * This set a color to a given regex
- * @param {String} regex
- */
- colorRegex(regex) {
- regex = regex.toString();
- const starts = (
-
- {regex.split('(')[0]}
-
- );
- let valuesArray = regex.match(/\(((?!<\/span>).)*?\)(?!<\/span>)/gim);
- const result = [starts];
- for (let i = 0, len = valuesArray.length; i < len; i++) {
- const coloredString = (
-
- {valuesArray[i]}
-
- );
- result.push(coloredString);
- }
- return result;
- }
-
- /**
- * Changes between decoders
- * @param {Number} name
- */
- changeBetweenDecoders(name) {
- this.setState({ currentDecoder: name });
- }
-
- render() {
- const { decoderInfo, isLoading } = this.props.state;
- const currentDecoder =
- this.state && this.state.currentDecoder ? this.state.currentDecoder : decoderInfo.current;
- const decoders = decoderInfo.affected_items;
- const currentDecoderArr = decoders.filter((r) => {
- return r.name === currentDecoder;
- });
- const currentDecoderInfo = currentDecoderArr[0];
- const { position, details, filename, name, relative_dirname } = currentDecoderInfo;
- const columns = this.columns;
-
- const onClickRow = (item) => {
- return {
- onClick: () => {
- this.changeBetweenDecoders(item.name);
- },
- };
- };
-
- return (
-
-
-
- {/* Decoder description name */}
-
-
-
-
-
- {
- window.location.href = window.location.href.replace(
- new RegExp('redirectRule=' + '[^&]*'),
- ''
- );
- this.props.cleanInfo();
- }}
- />
-
- {name}
-
-
-
-
- {/* Cards */}
-
-
- {/* General info */}
-
-
- Information
-
- }
- paddingSize="none"
- initialIsOpen={true}
- >
-
- {this.renderInfo(position, filename, relative_dirname)}
-
-
-
-
-
-
-
- Details
-
- }
- paddingSize="none"
- initialIsOpen={true}
- >
- {this.renderDetails(details)}
-
-
-
- {/* Table */}
-
-
-
- Related decoders
-
- }
- paddingSize="none"
- initialIsOpen={true}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-const mapStateToProps = (state) => {
- return {
- state: state.rulesetReducers,
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- updateFileContent: (content) => dispatch(updateFileContent(content)),
- cleanFileContent: () => dispatch(cleanFileContent()),
- updateFilters: (filters) => dispatch(updateFilters(filters)),
- cleanFilters: () => dispatch(cleanFilters()),
- cleanInfo: () => dispatch(cleanInfo()),
- };
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(WzDecoderInfo);
diff --git a/public/controllers/management/components/management/ruleset/main-ruleset.js b/public/controllers/management/components/management/ruleset/main-ruleset.js
deleted file mode 100644
index 35a7ad3211..0000000000
--- a/public/controllers/management/components/management/ruleset/main-ruleset.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component } from 'react';
-// Redux
-import store from '../../../../../redux/store';
-import WzReduxProvider from '../../../../../redux/wz-redux-provider';
-//Wazuh ruleset tables(rules, decoder, lists)
-import WzRulesetOverview from './ruleset-overview';
-//Information about rule or decoder
-import WzRuleInfo from './rule-info';
-import WzDecoderInfo from './decoder-info';
-import WzRulesetEditor from './ruleset-editor';
-import WzListEditor from './list-editor';
-
-export default class WzRuleset extends Component {
- _isMount = false;
- constructor(props) {
- super(props);
- this.state = {}; //Init state empty to avoid fails when try to read any parameter and this.state is not defined yet
- this.store = store;
- }
-
- UNSAFE_componentWillMount() {
- this._isMount = true;
- this.store.subscribe(() => {
- const state = this.store.getState().rulesetReducers;
- if (this._isMount) {
- this.setState(state);
- this.setState({ selectedTabId: state.section });
- }
- });
- }
-
- componentWillUnmount() {
- this._isMount = false;
- // When the component is going to be unmounted the ruleset state is reset
- const { ruleInfo, decoderInfo, listInfo, fileContent, addingRulesetFile } = this.state;
- if (
- !window.location.href.includes('rules?tab=rules') &&
- (!ruleInfo && !decoderInfo && !listInfo && !fileContent, !addingRulesetFile)
- ) {
- this.store.dispatch({ type: 'RESET' });
- }
- }
-
- render() {
- const { ruleInfo, decoderInfo, listInfo, fileContent, addingRulesetFile } = this.state;
-
- return (
-
- {(ruleInfo && ) ||
- (decoderInfo && ) ||
- (listInfo && ) ||
- ((fileContent || addingRulesetFile) && (
-
- )) || }
-
- );
- }
-}
diff --git a/public/controllers/management/components/management/ruleset/main-ruleset.tsx b/public/controllers/management/components/management/ruleset/main-ruleset.tsx
new file mode 100644
index 0000000000..5b95c11e3b
--- /dev/null
+++ b/public/controllers/management/components/management/ruleset/main-ruleset.tsx
@@ -0,0 +1,55 @@
+/*
+ * Wazuh app - React component for main Rules view.
+ * 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 } from 'react';
+import WzReduxProvider from '../../../../../redux/wz-redux-provider';
+import WzRulesetOverview from './views/ruleset-overview';
+import WzFileEditor from '../common/file-editor';
+import { SECTION_RULES_SECTION } from '../common/constants';
+
+
+export default function WzRuleset({ clusterStatus, logtestProps }) {
+
+ const [fileContent, setFileContent] = useState(false);
+ const [addingFile, setAddingFile] = useState(false);
+ const [showingFiles, setShowingFiles] = useState(false);
+
+ const cleanEditState = () => {
+ setFileContent(false);
+ setAddingFile(false);
+ }
+
+ return (
+
+ {
+ ((fileContent || addingFile) && (
+ { setFileContent(fileContent) }}
+ cleanEditState={() => cleanEditState()}
+ />
+ )) || (
+ { setFileContent(fileContent) }}
+ updateAddingFile={(addingFile) => { setAddingFile(addingFile) }}
+ setShowingFiles={() => { setShowingFiles(!showingFiles) }}
+ showingFiles={showingFiles}
+ />
+ )
+ }
+
+ );
+}
diff --git a/public/controllers/management/components/management/ruleset/ruleset-filter-bar.js b/public/controllers/management/components/management/ruleset/ruleset-filter-bar.js
deleted file mode 100644
index a7ca6ad053..0000000000
--- a/public/controllers/management/components/management/ruleset/ruleset-filter-bar.js
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component } from 'react';
-
-import { EuiComboBox, EuiFormRow } from '@elastic/eui';
-import { connect } from 'react-redux';
-
-import {
- updateLoadingStatus,
- updateFilters,
- updateError
-} from '../../../../../redux/actions/rulesetActions';
-
-import { RulesetHandler, RulesetResources } from './utils/ruleset-handler';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-
-class WzRulesetFilterBar extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- isInvalid: false,
- selectedOptions: []
- };
-
- this.rulesetHandler = new RulesetHandler(this.props.state.section);
- this.availableOptions = {
- rules: [
- 'nist-800-53',
- 'hipaa',
- 'gdpr',
- 'pci',
- 'gpg13',
- 'tsc',
- 'group',
- 'level',
- 'path',
- 'file'
- ],
- decoders: ['path', 'file'],
- lists: []
- };
- this.notValidMessage = false;
- }
-
- componentDidMount() {
- this.buildSelectedOptions(this.props.state.filters); // If there are any filter in the redux store it will be restored when the component was mounted
- }
-
- isValid = value => {
- const { section, showingFiles } = this.props.state;
- if (section === 'lists' || showingFiles) return true; //There are not filters for lists
- const lowerValue = value.toLowerCase();
- const availableOptions = this.availableOptions[
- this.props.state.section
- ].toString();
- this.notValidMessage = false;
- const options = this.availableOptions[this.props.state.section];
- const valueSplit = lowerValue.split(':');
- const oneTwoDots = valueSplit.length - 1 === 1; // Has : once
- const moreTwoDots = valueSplit.length - 1 > 1; // Has : several times
- const notAvailable = !options.includes(valueSplit[0]); // Not include in the available options
- if (moreTwoDots || (oneTwoDots && notAvailable)) {
- if (oneTwoDots) {
- this.notValidMessage = `${
- valueSplit[0]
- } is a not valid filter, the available filters are: ${availableOptions}`;
- } else {
- this.notValidMessage = 'Only allow ":" once';
- }
- return false;
- }
- return true;
- };
-
- /**
- * Set a valid array of objects for the options in the combo box [{label: value}, {label: value}]
- */
- async buildSelectedOptions(filters) {
- try {
- const selectedOptions = [];
- Object.keys(filters).forEach(key => {
- const value = filters[key];
- const option = key === 'search' ? value : `${key}:${value}`;
- const newOption = {
- label: option
- };
- selectedOptions.push(newOption);
- });
- this.setState({ selectedOptions });
- //const result = await this.wzReq.apiReq('GET', this.paths[section], {})
- if (Object.keys(filters).length) await this.fetchItems(filters);
- } catch (error) {
- const options = {
- context: `${WzRulesetFilterBar.name}.buildSelectedOptions`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.UI,
- error: {
- error: error,
- message: error.message || error,
- title: `Error building selected options: ${error.message || error}`,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- /**
- * Fetch items (rules, decoders)
- * @param {Object} filters
- */
- async fetchItems(filters) {
- try {
- const { section } = this.props.state;
- let fetcher = this.rulesetHandler.getResource;
- if (section === RulesetResources.LISTS) fetcher = this.rulesetHandler.getFiles; // If the sections is lists the fetcher changes
- this.props.updateLoadingStatus(true);
- const result = await fetcher(filters);
- this.props.updateLoadingStatus(false);
- } catch (error) {
- this.props.updateError(error);
- throw error;
- }
- }
-
- /**
- * When any element is removed from the this.state.selectedOptions is removed too from this.props.state.filters
- * @param {Array} selectedOptions
- */
- async cleanCurrentOption(selectedOptions) {
- try {
- const remainingKeys = [];
- const currentOptions = { ...this.props.state.filters };
- selectedOptions.forEach(option => {
- const value = option.label;
- const valueSplit = value.split(':');
- const isSearch = valueSplit.length === 1;
- const keyToRemove = isSearch ? 'search' : valueSplit[0];
- remainingKeys.push(keyToRemove);
- });
- const currentOptiosnKeys = Object.keys(currentOptions);
- const keysToRemove = currentOptiosnKeys.filter(option => {
- return !remainingKeys.includes(option);
- });
- keysToRemove.forEach(key => delete currentOptions[key]);
- this.props.updateFilters(currentOptions);
- await this.fetchItems(currentOptions);
- } catch (error) {
- const options = {
- context: `${WzRulesetFilterBar.name}.cleanCurrentOption`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.UI,
- error: {
- error: error,
- message: `Error cleaning current options: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- onCreateOption = searchValue => {
- const isList = this.props.state.section === 'lists';
- const lowerValue = searchValue.toLowerCase();
- const currentOptions = { ...this.props.state.filters };
- const creatingSplit = lowerValue.split(':');
- let key = 'search';
- let value;
- if (!isList) {
- if (creatingSplit.length > 1) {
- key = creatingSplit[0];
- value = creatingSplit[1];
- } else {
- value = creatingSplit[0];
- }
- if (!this.isValid(lowerValue) || !value) return false; // Return false to explicitly reject the user's input.
- } else {
- value = lowerValue;
- }
- currentOptions[key] = value;
- this.props.updateFilters(currentOptions);
- this.buildSelectedOptions(currentOptions);
- };
-
- // When writting in the filter bar
- onSearchChange = searchValue => {
- if (!searchValue) {
- this.setState({
- isInvalid: false
- });
-
- return;
- }
-
- this.setState({
- isInvalid: !this.isValid(searchValue)
- });
- };
-
- onChange = selectedOptions => {
- this.setState({
- selectedOptions,
- isInvalid: false
- });
- this.cleanCurrentOption(selectedOptions);
- };
-
- render() {
- const { section, showingFiles } = this.props.state;
- const { selectedOptions, isInvalid } = this.state;
- const options = !Object.keys(this.props.state.filters).length
- ? []
- : selectedOptions;
- const filters = !showingFiles
- ? `Filter ${section}...`
- : `Search ${section} files...`;
-
- return (
-
-
-
- );
- }
-}
-
-const mapStateToProps = state => {
- return {
- state: state.rulesetReducers
- };
-};
-
-const mapDispatchToProps = dispatch => {
- return {
- updateLoadingStatus: status => dispatch(updateLoadingStatus(status)),
- updateFilters: filters => dispatch(updateFilters(filters)),
- updateError: error => dispatch(updateError(error))
- };
-};
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(WzRulesetFilterBar);
diff --git a/public/controllers/management/components/management/ruleset/ruleset-overview.js b/public/controllers/management/components/management/ruleset/ruleset-overview.js
deleted file mode 100644
index d6d31a2924..0000000000
--- a/public/controllers/management/components/management/ruleset/ruleset-overview.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import React, { Component } from 'react';
-// Eui components
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiPanel,
- EuiPage,
- EuiText,
- EuiTitle,
- EuiLoadingSpinner,
- EuiSpacer
-} from '@elastic/eui';
-
-import { connect } from 'react-redux';
-
-// Wazuh components
-import WzRulesetTable from './ruleset-table';
-import WzRulesetSearchBar from './ruleset-search-bar';
-import WzRulesetActionButtons from './actions-buttons';
-import './ruleset-overview.scss';
-import { withUserAuthorizationPrompt, withGlobalBreadcrumb } from '../../../../../components/common/hocs';
-import WzRestartClusterManagerCallout from '../../../../../components/common/restart-cluster-manager-callout';
-import { compose } from 'redux';
-import { resourceDictionary } from './utils/ruleset-handler';
-
-class WzRulesetOverview extends Component {
- sectionNames = {
- rules: 'Rules',
- decoders: 'Decoders',
- lists: 'CDB lists'
- };
-
- constructor(props) {
- super(props);
- this.state = {
- totalItems: 0,
- showWarningRestart: false
- }
- }
-
- updateRestartManagers(showWarningRestart){
- this.setState({ showWarningRestart });
- }
-
- render() {
- const { section } = this.props.state;
- const { totalItems } = this.state;
-
- return (
-
-
-
-
-
- {this.sectionNames[section]} {totalItems === false ? : ({totalItems})}
-
-
-
- this.updateRestartManagers(showWarningRestart)}/>
-
-
-
-
- {`From here you can manage your ${section}.`}
-
-
-
- {this.state.showWarningRestart && (
- <>
-
- this.updateRestartManagers(false)}
- onRestartedError={() => this.updateRestartManagers(true)}
- />
-
- >
- )}
-
-
-
- this.setState({ totalItems })}
- />
-
-
-
-
- );
- }
-}
-
-const mapStateToProps = state => {
- return {
- state: state.rulesetReducers
- };
-};
-
-export default compose(
- connect(
- mapStateToProps
- ),
- withGlobalBreadcrumb(props => {
- const sectionNames = {
- rules: 'Rules',
- decoders: 'Decoders',
- lists: 'CDB lists'
- }
- return [
- { text: '' },
- { text: 'Management', href: '#/manager' },
- { text: sectionNames[props.state.section] }
- ];
- }),
- withUserAuthorizationPrompt((props) => [{action: `${props.state.section}:read`, resource: resourceDictionary[props.state.section].permissionResource('*')}])
-)(WzRulesetOverview);
diff --git a/public/controllers/management/components/management/ruleset/ruleset-popover-filters.tsx b/public/controllers/management/components/management/ruleset/ruleset-popover-filters.tsx
deleted file mode 100644
index 945b03eac3..0000000000
--- a/public/controllers/management/components/management/ruleset/ruleset-popover-filters.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Wazuh app - React component for show filter list.
- * 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 { connect } from 'react-redux';
-import {
- EuiFlexItem,
- EuiPopover,
- EuiButton,
- EuiButtonEmpty
-} from '@elastic/eui';
-
-class WzPopoverFilters extends Component {
- filters: {
- rules: { label: string; value: string; }[];
- decoders: { label: string; value: string; }[];
- };
-
- constructor(props) {
- super(props);
- this.state = {
- isPopoverOpen: false
- }
- this.filters = {
- rules: [
- { label: 'File', value: 'file' }, { label: 'Path', value: 'path' }, { label: 'Level', value: 'level' },
- { label: 'Group', value: 'group' }, { label: 'PCI control', value: 'pci' }, { label: 'GDPR', value: 'gdpr' }, { label: 'HIPAA', value: 'hipaa' }, { label: 'NIST-800-53', value: 'nist-800-53' }, { label: 'TSC', value: 'tsc' }
- ],
- decoders: [
- { label: 'File', value: 'file' }, { label: 'Path', value: 'path' }
- ]
- };
- }
-
- onButtonClick() {
- this.setState({
- isPopoverOpen: !this.state['isPopoverOpen'],
- });
- }
-
- closePopover() {
- this.setState({
- isPopoverOpen: false,
- });
- }
-
- render() {
- const { section } = this.props['state'];
- const button = (
- this.onButtonClick()}
- iconType="logstashFilter"
- aria-label="Filter">
- Filters
-
- );
-
- return (
-
-
- {this.filters[section].map((filter, idx) => (
-
- null}>
- {filter.label}
-
-
- ))}
-
-
- );
- }
-
-}
-
-const mapStateToProps = (state) => {
- return {
- state: state.rulesetReducers,
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- };
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(WzPopoverFilters);
diff --git a/public/controllers/management/components/management/ruleset/ruleset-search-bar.js b/public/controllers/management/components/management/ruleset/ruleset-search-bar.js
deleted file mode 100644
index 8331cefee1..0000000000
--- a/public/controllers/management/components/management/ruleset/ruleset-search-bar.js
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-
-import {
- updateFilters,
- updateIsProcessing
-} from '../../../../../redux/actions/rulesetActions';
-import { WzRequest } from '../../../../../react-services/wz-request';
-import { WzSearchBar } from '../../../../../components/wz-search-bar';
-
-class WzRulesetSearchBar extends Component {
- constructor(props) {
- super(props);
- }
-
- rulesItems = [
- {
- type: 'params',
- label: 'status',
- description: 'Filters the rules by status.',
- values: ['enabled', 'disabled']
- },
- {
- type: 'params',
- label: 'group',
- description: 'Filters the rules by group',
- values: async value => {
- const filter = { limit: 30 };
- if (value) {
- filter['search'] = value;
- }
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/groups', filter);
- return (((result || {}).data || {}).data || {}).affected_items;
- },
- },
- {
- type: 'params',
- label: 'level',
- description: 'Filters the rules by level',
- values: [...Array(16).keys()]
- },
- {
- type: 'params',
- label: 'filename',
- description: 'Filters the rules by file name.',
- values: async value => {
- const filter = { limit: 30 };
- if (value) {
- filter['search'] = value;
- }
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/files', filter);
- return (((result || {}).data || {}).data || {}).affected_items.map((item) => {return item.filename});
- },
- },
- {
- type: 'params',
- label: 'relative_dirname',
- description: 'Path of the rules files',
- values: async value => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/manager/configuration', {params: {
- section: 'ruleset',
- field: 'rule_dir'
- }});
- return (((result || {}).data || {}).data || {}).affected_items[0].ruleset.rule_dir;
- }
- },
- {
- type: 'params',
- label: 'hipaa',
- description: 'Filters the rules by HIPAA requirement',
- values: async () => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/requirement/hipaa', {});
- return (((result || {}).data || {}).data || {}).affected_items;
- }
- },
- {
- type: 'params',
- label: 'gdpr',
- description: 'Filters the rules by GDPR requirement',
- values: async () => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/requirement/gdpr', {});
- return (((result || {}).data || {}).data || {}).affected_items;
- }
- },
- {
- type: 'params',
- label: 'nist-800-53',
- description: 'Filters the rules by NIST requirement',
- values: async () => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/requirement/nist-800-53', {});
- return (((result || {}).data || {}).data || {}).affected_items;
- }
- },
- {
- type: 'params',
- label: 'gpg13',
- description: 'Filters the rules by GPG requirement',
- values: async () => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/requirement/gpg13', {});
- return (((result || {}).data || {}).data || {}).affected_items;
- }
- },
- {
- type: 'params',
- label: 'pci_dss',
- description: 'Filters the rules by PCI DSS requirement',
- values: async () => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/requirement/pci_dss', {});
- return (((result || {}).data || {}).data || {}).affected_items;
- }
- },
- {
- type: 'params',
- label: 'tsc',
- description: 'Filters the rules by TSC requirement',
- values: async () => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/requirement/tsc', {});
- return (((result || {}).data || {}).data || {}).affected_items;
- }
- },
- {
- type: 'params',
- label: 'mitre',
- description: 'Filters the rules by MITRE requirement',
- values: async () => {
- const result = await WzRequest.apiReq('GET', '/rules/requirement/mitre', {});
- return (((result || {}).data || {}).data || {}).affected_items;
- }
- }
- ];
- rulesFiles = [
- {
- type: 'params',
- label: 'filename',
- description: 'Filters the rules by file name.',
- values: async value => {
- const filter = { limit: 30 };
- if (value) {
- filter['search'] = value;
- }
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/rules/files', filter);
- return (((result || {}).data || {}).data || {}).affected_items.map((item) => {return item.filename});
- },
- },
- ];
-
- decodersItems = [
- {
- type: 'params',
- label: 'filename',
- description: 'Filters the decoders by file name.',
- values: async value => {
- const filter = { limit: 30 };
- if (value) {
- filter['search'] = value;
- }
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/decoders/files', filter);
- return (((result || {}).data || {}).data || {}).affected_items.map((item) => {return item.filename});
- },
- },
- {
- type: 'params',
- label: 'relative_dirname',
- description: 'Path of the decoders files.',
- values: async () => {
- const wzReq = (...args) => WzRequest.apiReq(...args);
- const result = await wzReq('GET', '/manager/configuration', {params: {
- section: 'ruleset',
- field: 'decoder_dir'
- }});
- return (((result || {}).data || {}).data || {}).affected_items[0].ruleset.decoder_dir;
- }
- },
- {
- type: 'params',
- label: 'status',
- description: 'Filters the decoders by status.',
- values: ['enabled', 'disabled']
- }
- ];
-
- apiSuggestsItems = {
- items: {
- rules: this.rulesItems,
- decoders: this.decodersItems,
- list: []
- },
- files: {
- rule: this.rulesFiles,
- decoders: [],
- list: []
- }
- };
-
- buttonOptions = {
- rules: [{label: "Custom rules", field:"relative_dirname", value:"etc/rules"}, ],
- decoders: [{label: "Custom decoders", field:"relative_dirname", value:"etc/decoders"}, ],
- list: []
- };
-
- render() {
- const { section, showingFiles, filters } = this.props.state;
- const type = showingFiles ? 'files' : 'items';
- const suggestions = this.apiSuggestsItems[type][section] || [];
- const buttonOptions = this.buttonOptions[section];
- return (
-
- );
- }
-}
-
-const mapStateToProps = state => {
- return {
- state: state.rulesetReducers
- };
-};
-
-const mapDispatchToProps = dispatch => {
- return {
- updateFilters: filters => dispatch(updateFilters(filters)),
- updateIsProcessing: state => dispatch(updateIsProcessing(state))
- };
-};
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(WzRulesetSearchBar);
diff --git a/public/controllers/management/components/management/ruleset/ruleset-table.js b/public/controllers/management/components/management/ruleset/ruleset-table.js
deleted file mode 100644
index d88583b605..0000000000
--- a/public/controllers/management/components/management/ruleset/ruleset-table.js
+++ /dev/null
@@ -1,441 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component } from 'react';
-import { EuiBasicTable, EuiCallOut, EuiOverlayMask, EuiConfirmModal } from '@elastic/eui';
-
-import { connect } from 'react-redux';
-import { RulesetHandler, RulesetResources, resourceDictionary } from './utils/ruleset-handler';
-import { getToasts } from '../../../../../kibana-services';
-
-import {
- updateIsProcessing,
- updateShowModal,
- updateDefaultItems,
- updateListContent,
- updateFileContent,
- updateListItemsForRemove,
- updateRuleInfo,
- updateDecoderInfo,
-} from '../../../../../redux/actions/rulesetActions';
-
-import RulesetColums from './utils/columns';
-import { WzRequest } from '../../../../../react-services/wz-request';
-import { filtersToObject } from '../../../../../components/wz-search-bar';
-import { withUserPermissions } from '../../../../../components/common/hocs/withUserPermissions';
-import { WzUserPermissions } from '../../../../../react-services/wz-user-permissions';
-import { compose } from 'redux';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-class WzRulesetTable extends Component {
- _isMounted = false;
- constructor(props) {
- super(props);
- this.wzReq = (...args) => WzRequest.apiReq(...args);
- this.state = {
- items: [],
- pageSize: 15,
- pageIndex: 0,
- totalItems: 0,
- isLoading: false,
- isRedirect: false,
- };
- this.paths = {
- rules: '/rules',
- decoders: '/decoders',
- lists: '/lists/files',
- };
- this.extraSectionPrefixResource = {
- rules: 'rule:file',
- decoders: 'decoder:file',
- lists: 'list:file',
- };
- this.rulesetHandler = new RulesetHandler(this.props.state.section);
- }
-
- async componentDidMount() {
- this._isMounted = true;
- this.props.updateIsProcessing(true);
- if (this.props.state.section === RulesetResources.RULES) {
- const regex = new RegExp('redirectRule=' + '[^&]*');
- const match = window.location.href.match(regex);
- if (match && match[0]) {
- this._isMounted && this.setState({ isRedirect: true });
- const id = match[0].split('=')[1];
- try {
- const result = await this.rulesetHandler.getResource({
- params: {
- rule_ids: id,
- },
- });
- const items = result.data.affected_items || [];
- if (items.length) {
- const info = await this.rulesetHandler.getResource({
- params: {
- filename: items[0].filename,
- },
- });
- if (info.data) {
- Object.assign(info.data, { current: parseInt(id) });
- }
- this.props.updateRuleInfo(info.data);
- }
- } catch (error) {
- const options = {
- context: `${WzRulesetTable.name}.componentDidMount`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: `Error getting resources: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- this._isMounted && this.setState({ isRedirect: false });
- }
- }
- }
-
- async componentDidUpdate(prevProps) {
- const { isProcessing, section, showingFiles, filters } = this.props.state;
-
- const processingChange =
- prevProps.state.isProcessing !== isProcessing ||
- (prevProps.state.isProcessing && isProcessing);
- const sectionChanged = prevProps.state.section !== section;
- const showingFilesChanged = prevProps.state.showingFiles !== showingFiles;
- const filtersChanged = prevProps.state.filters !== filters;
-
- try {
- if (
- (this._isMounted && processingChange && isProcessing) ||
- sectionChanged ||
- filtersChanged
- ) {
- if (sectionChanged || showingFilesChanged || filtersChanged) {
- this._isMounted &&
- (await this.setState({
- pageSize: this.state.pageSize,
- pageIndex: 0,
- sortDirection: null,
- sortField: null,
- }));
- }
- this._isMounted && this.setState({ isLoading: true });
- this.props.updateIsProcessing(false);
- await this.getItems();
- }
- } catch (error) {
- const options = {
- context: `${WzRulesetTable.name}.componentDidUpdate`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: `Error getting items: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- componentWillUnmount() {
- this._isMounted = false;
- }
-
- async getItems() {
- const { section, showingFiles } = this.props.state;
-
- this._isMounted &&
- this.setState({
- items: [],
- });
- this.props.updateTotalItems(false);
-
- const rawItems = await this.wzReq(
- 'GET',
- `${this.paths[this.props.request]}${showingFiles ? '/files' : ''}`,
- { params: this.buildFilter() }
- ).catch((error) => {
- throw new Error(`Error when get the items of ${section}`);
- });
-
- const { affected_items = [], total_affected_items = 0 } =
- ((rawItems || {}).data || {}).data || {};
- this.props.updateTotalItems(total_affected_items);
- this._isMounted &&
- this.setState({
- items: affected_items,
- totalItems: total_affected_items,
- isLoading: false,
- });
- }
-
- async setDefaultItems() {
- try {
- const requestDefaultItems = await this.wzReq('GET', '/manager/configuration', {
- wait_for_complete: false,
- section: 'ruleset',
- field: 'list',
- });
-
- const defaultItems = ((requestDefaultItems || {}).data || {}).data;
- this.props.updateDefaultItems(defaultItems);
- } catch (error) {
- throw error;
- }
- }
-
- buildFilter() {
- const { pageSize, pageIndex } = this.state;
- const { filters } = this.props.state;
- const filter = {
- offset: pageIndex * pageSize,
- limit: pageSize,
- ...this.buildSortFilter(),
- ...filtersToObject(filters),
- };
-
- return filter;
- }
-
- buildSortFilter() {
- const { sortDirection, sortField } = this.state;
- const sortFilter = {};
- if (sortField) {
- const direction = sortDirection === 'asc' ? '+' : '-';
- sortFilter['sort'] = direction + sortField;
- }
-
- return sortFilter;
- }
-
- onTableChange = ({ page = {}, sort = {} }) => {
- const { index: pageIndex, size: pageSize } = page;
- const { field: sortField, direction: sortDirection } = sort;
- this.setState({ pageSize, pageIndex, sortDirection, sortField });
- this.props.updateIsProcessing(true);
- };
-
- getColumns() {
- const { section, showingFiles } = this.props.state;
- const rulesetColums = new RulesetColums(this.props).columns;
- const columns = showingFiles ? rulesetColums.files : rulesetColums[section];
- return columns;
- }
-
- render() {
- const { error } = this.props.state;
- const {
- items,
- pageSize,
- totalItems,
- pageIndex,
- sortField,
- sortDirection,
- isLoading,
- isRedirect,
- } = this.state;
- const columns = this.getColumns();
- const message = isLoading ? null : 'No results...';
- const pagination = {
- pageIndex: pageIndex,
- pageSize: pageSize,
- totalItemCount: totalItems,
- pageSizeOptions: [10, 15, 25, 50, 100],
- };
- const sorting = !!sortField
- ? {
- sort: {
- field: sortField,
- direction: sortDirection,
- },
- }
- : {};
-
- if (!error) {
- const itemList = this.props.state.itemList;
-
- const getRowProps = (item) => {
- const { id, name } = item;
-
- const getRequiredPermissions = (item) => {
- const { section } = this.props.state;
- const { permissionResource } = resourceDictionary[section];
- return [
- {
- action: `${section}:read`,
- resource: permissionResource(item.name),
- },
- ];
- };
-
- const updateInfo = async () => {
- if (this.isLoading) return;
- this.setState({ isLoading: true });
- const { section } = this.props.state;
- section === RulesetResources.RULES && (window.location.href = `${window.location.href}&redirectRule=${id}`);
- try {
- if (section === RulesetResources.LISTS) {
- const result = await this.rulesetHandler.getFileContent(item.filename);
- const file = {
- name: item.filename,
- content: result,
- path: item.relative_dirname,
- };
- this.props.updateListContent(file);
- } else {
- const result = await this.rulesetHandler.getResource({
- params: {
- filename: item.filename,
- },
- });
- if (result.data) {
- Object.assign(result.data, { current: id || name });
- }
- if (section === RulesetResources.RULES) this.props.updateRuleInfo(result.data);
- if (section === RulesetResources.DECODERS) this.props.updateDecoderInfo(result.data);
- }
- } catch (error) {
- const options = {
- context: `${WzRulesetTable.name}.updateInfo`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: `Error updating info: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- this.setState({ isLoading: false });
- }
- this.setState({ isLoading: false });
- };
-
- return {
- 'data-test-subj': `row-${id || name}`,
- className: 'customRowClass',
- onClick: !WzUserPermissions.checkMissingUserPermissions(
- getRequiredPermissions(item),
- this.props.userPermissions
- )
- ? updateInfo
- : undefined,
- };
- };
-
- return (
-
-
- {this.props.state.showModal ? (
-
- this.props.updateShowModal(false)}
- onConfirm={() => {
- this.removeItems(itemList);
- this.props.updateShowModal(false);
- }}
- cancelButtonText="No, don't do it"
- confirmButtonText="Yes, do it"
- defaultFocusedButton="cancel"
- buttonColor="danger"
- >
- These items will be removed
-
- {itemList.map(function (item, i) {
- return
{item.filename ? item.filename : item.name};
- })}
-
-
-
- ) : null}
-
- );
- } else {
- return ;
- }
- }
-
- showToast = (color, title, text, time) => {
- getToasts().add({
- color: color,
- title: title,
- text: text,
- toastLifeTimeMs: time,
- });
- };
-
- async removeItems(items) {
- try {
- this.setState({ isLoading: true });
- const results = items.map(async (item, i) => {
- await this.rulesetHandler.deleteFile(item.filename || item.name);
- });
-
- Promise.all(results).then((completed) => {
- this.props.updateIsProcessing(true);
- this.showToast('success', 'Success', 'Deleted successfully', 3000);
- });
- } catch (error) {
- const options = {
- context: `${WzRulesetTable.name}.removeItems`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: `Error deleting item: ${error.message || error}`,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-}
-
-const mapStateToProps = (state) => {
- return {
- state: state.rulesetReducers,
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- updateDefaultItems: (defaultItems) => dispatch(updateDefaultItems(defaultItems)), //TODO: Research to remove
- updateIsProcessing: (isProcessing) => dispatch(updateIsProcessing(isProcessing)),
- updateShowModal: (showModal) => dispatch(updateShowModal(showModal)),
- updateFileContent: (fileContent) => dispatch(updateFileContent(fileContent)),
- updateListContent: (listInfo) => dispatch(updateListContent(listInfo)),
- updateListItemsForRemove: (itemList) => dispatch(updateListItemsForRemove(itemList)),
- updateRuleInfo: (rule) => dispatch(updateRuleInfo(rule)),
- updateDecoderInfo: (rule) => dispatch(updateDecoderInfo(rule)),
- };
-};
-
-export default compose(
- connect(mapStateToProps, mapDispatchToProps),
- withUserPermissions
-)(WzRulesetTable);
diff --git a/public/controllers/management/components/management/ruleset/section-selector.js b/public/controllers/management/components/management/ruleset/section-selector.js
deleted file mode 100644
index 7333ff0d7b..0000000000
--- a/public/controllers/management/components/management/ruleset/section-selector.js
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component } from 'react';
-import { EuiSelect } from '@elastic/eui';
-
-import { connect } from 'react-redux';
-import {
- updateRulesetSection,
- updateLoadingStatus,
- toggleShowFiles,
- cleanFilters,
- updateError,
- updateIsProcessing
-} from '../../../../redux/actions/rulesetActions';
-
-import { WzRequest } from '../../../../react-services/wz-request';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-
-class WzSectionSelector extends Component {
- constructor(props) {
- super(props);
-
- this.sections = [
- { value: 'rules', text: 'Rules' },
- { value: 'decoders', text: 'Decoders' },
- { value: 'lists', text: 'CDB lists' }
- ];
-
- this.paths = {
- rules: '/rules',
- decoders: '/decoders',
- lists: '/lists/files'
- };
-
- this.wzReq = WzRequest;
- }
-
- /**
- * Fetch the data for a section: rules, decoders, lists...
- * @param {String} newSection
- */
- async fetchData(newSection) {
- try {
- const currentSection = this.props.state.section;
- if (
- Object.keys(this.props.state.filters).length &&
- newSection === currentSection
- )
- return; // If there's any filter and the section is de same doesn't fetch again
- this.props.changeSection(newSection);
- this.props.updateLoadingStatus(true);
- const result = await this.wzReq.apiReq('GET', this.paths[newSection], {});
- const items = result.data.data.items;
-
- this.props.toggleShowFiles(false);
- this.props.changeSection(newSection);
- this.props.updateLoadingStatus(false);
- } catch (error) {
- this.props.updateError(error);
- throw error;
- }
- }
-
- onChange = async e => {
- try {
- const section = e.target.value;
- this.props.cleanFilters();
- this.props.updateIsProcessing(true);
- this.fetchData(section);
- }catch (error){
- const options = {
- context: `${WzSectionSelector.name}.onChange`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: error.message || error,
- title: `Error fetching data: ${error.message || error}`,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- };
-
- render() {
- return (
-
- );
- }
-}
-
-const mapStateToProps = state => {
- return {
- state: state.rulesetReducers
- };
-};
-
-const mapDispatchToProps = dispatch => {
- return {
- changeSection: section => dispatch(updateRulesetSection(section)),
- updateLoadingStatus: status => dispatch(updateLoadingStatus(status)),
- toggleShowFiles: status => dispatch(toggleShowFiles(status)),
- cleanFilters: () => dispatch(cleanFilters()),
- updateError: error => dispatch(updateError(error)),
- updateIsProcessing: isProcessing =>
- dispatch(updateIsProcessing(isProcessing))
- };
-};
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(WzSectionSelector);
diff --git a/public/controllers/management/components/management/ruleset/utils/columns.tsx b/public/controllers/management/components/management/ruleset/utils/columns.tsx
deleted file mode 100644
index 1a3160b636..0000000000
--- a/public/controllers/management/components/management/ruleset/utils/columns.tsx
+++ /dev/null
@@ -1,481 +0,0 @@
-import React from 'react';
-import { EuiToolTip, EuiButtonIcon, EuiLink, EuiBadge } from '@elastic/eui';
-import { resourceDictionary, RulesetHandler, RulesetResources } from './ruleset-handler';
-import exportCsv from '../../../../../../react-services/wz-csv';
-import { WzButtonPermissions } from '../../../../../../components/common/permissions/button';
-import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
-import { UIErrorLog, UILogLevel, UIErrorSeverity, UI_ERROR_SEVERITIES } from '../../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../../common/constants';
-
-export default class RulesetColumns {
- constructor(tableProps) {
- this.tableProps = tableProps;
-
- this.buildColumns = () => {
- this.columns = {
- rules: [
- {
- field: 'id',
- name: 'ID',
- align: 'left',
- sortable: true,
- width: '5%'
- },
- {
- field: 'description',
- name: 'Description',
- align: 'left',
- sortable: true,
- width: '30%',
- render: (value, item) => {
- if(value === undefined) return '';
- const regex = /\$(.*?)\)/g;
- let result = value.match(regex);
- let haveTooltip = false;
- let toolTipDescription = false;
- if(result !== null) {
- haveTooltip = true;
- toolTipDescription = value;
- for (const oldValue of result) {
- let newValue = oldValue.replace('$(',``);
- newValue = newValue.replace(')', ' ');
- value = value.replace(oldValue, newValue);
- }
- }
- return (
-
- {haveTooltip === false ?
- :
-
-
-
- }
-
- );
- }
- },
- {
- field: 'groups',
- name: 'Groups',
- align: 'left',
- sortable: false,
- width: '10%'
- },
- {
- name: 'Regulatory compliance',
- render: this.buildComplianceBadges
- },
- {
- field: 'level',
- name: 'Level',
- align: 'left',
- sortable: true,
- width: '5%'
- },
- {
- field: 'filename',
- name: 'File',
- align: 'left',
- sortable: true,
- width: '15%',
- render: (value, item) => {
- return (
- {
- try{
- ev.stopPropagation();
- const rulesetHandler = new RulesetHandler(RulesetResources.RULES);
- const result = await rulesetHandler.getFileContent(value);
- const file = { name: value, content: result, path: item.relative_dirname };
- this.tableProps.updateFileContent(file);
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Rules.readFileContent'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}>
- {value}
-
- );
- }
- },
- {
- field: 'relative_dirname',
- name: 'Path',
- align: 'left',
- sortable: true,
- width: '10%'
- }
- ],
- decoders: [
- {
- field: 'name',
- name: 'Name',
- align: 'left',
- sortable: true
- },
- {
- field: 'details.program_name',
- name: 'Program name',
- align: 'left',
- sortable: false
- },
- {
- field: 'details.order',
- name: 'Order',
- align: 'left',
- sortable: false
- },
- {
- field: 'filename',
- name: 'File',
- align: 'left',
- sortable: true,
- render: (value, item) => {
- return (
- {
- try{
- ev.stopPropagation();
- const rulesetHandler = new RulesetHandler(RulesetResources.DECODERS);
- const result = await rulesetHandler.getFileContent(value);
- const file = { name: value, content: result, path: item.relative_dirname };
- this.tableProps.updateFileContent(file);
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Decoders.readFileContent'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}>
- {value}
-
- );
- }
- },
- {
- field: 'relative_dirname',
- name: 'Path',
- align: 'left',
- sortable: true
- }
- ],
- lists: [
- {
- field: 'filename',
- name: 'Name',
- align: 'left',
- sortable: true
- },
- {
- field: 'relative_dirname',
- name: 'Path',
- align: 'left',
- sortable: true
- },
- {
- name: 'Actions',
- align: 'left',
- render: (item) => (
-
- {
- try{
- ev.stopPropagation();
- await exportCsv(`/lists?path=${item.relative_dirname}/${item.filename}`, [{_isCDBList: true, name: 'path', value: `${item.relative_dirname}/${item.filename}`}], item.filename)
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Lists.exportFile'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}
- color="primary"
- />
-
- )
- }
- ],
- files: [
- {
- field: 'filename',
- name: 'File',
- align: 'left',
- sortable: true
- },
- {
- name: 'Actions',
- align: 'left',
- render: item => {
- if (item.relative_dirname.startsWith('ruleset/')) {
- return (
- {
- try{
- ev.stopPropagation();
- const rulesetHandler = new RulesetHandler(this.tableProps.state.section);
- const result = await rulesetHandler.getFileContent(item.filename);
- const file = { name: item.filename, content: result, path: item.relative_dirname };
- this.tableProps.updateFileContent(file);
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Files.readFileContent'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}
- color="primary"
- />
- );
- } else {
- return (
-
- {
- try{
- ev.stopPropagation();
- const rulesetHandler = new RulesetHandler(this.tableProps.state.section);
- const result = await rulesetHandler.getFileContent(item.filename);
- const file = { name: item.filename, content: result, path: item.relative_dirname };
- this.tableProps.updateFileContent(file);
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Files.editFileContent'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}
- color="primary"
- />
- {
- try{
- ev.stopPropagation();
- this.tableProps.updateListItemsForRemove([item]);
- this.tableProps.updateShowModal(true);
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Files.deleteFile'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}
- color="danger"
- />
-
- );
- }
- }
- }
- ]
- };
-
- const getReadButtonPermissions = (item) => {
- const { section } = this.tableProps.state;
- const { permissionResource } = resourceDictionary[section];
- return [
- {
- action: `${section}:read`,
- resource: permissionResource(item.filename),
- },
- ];
- };
-
- const getEditButtonPermissions = (item) => {
- const { section } = this.tableProps.state;
- const { permissionResource } = resourceDictionary[section];
- return [
- {
- action: `${section}:read`,
- resource: permissionResource(item.filename),
- },
- { action: `${section}:update`, resource: permissionResource(item.filename) },
- ];
- };
-
- const getDeleteButtonPermissions = (item) => {
- const { section } = this.tableProps.state;
- const { permissionResource } = resourceDictionary[section];
- return [
- {
- action: `${section}:delete`,
- resource: permissionResource(item.filename),
- },
- ];
- };
-
- this.columns.lists[2] =
- {
- name: 'Actions',
- align: 'left',
- render: item => {
- const defaultItems = this.tableProps.state.defaultItems;
- return (
-
- {
- try{
- ev.stopPropagation();
- const rulesetHandler = new RulesetHandler(this.tableProps.state.section);
- const result = await rulesetHandler.getFileContent(item.filename);
- const file = { name: item.filename, content: result, path: item.relative_dirname };
- this.tableProps.updateListContent(file);
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Lists.editFileContent'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}
- color="primary"
- />
- {
- try{
- ev.stopPropagation();
- this.tableProps.updateListItemsForRemove([item]);
- this.tableProps.updateShowModal(true);
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Lists.deleteFile'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}
- color="danger"
- isDisabled={defaultItems.indexOf(`${item.relative_dirname}`) !== -1}
- />
- {
- try{
- ev.stopPropagation();
- await exportCsv(`/lists`, [{_isCDBList: true, name: 'filename', value: `${item.filename}`}], item.filename)
- }catch(error){
- const options: UIErrorLog = this.getErrorOptions(
- error,
- 'Lists.exportFile'
- );
- getErrorOrchestrator().handleError(options);
- }
- }}
- color="primary"
- />
-
- )
- }
- }
- };
-
-
- this.buildColumns();
- }
-
- /**
- * Build and return a new error options object, based on the actual error
- * and the context
- * @param error raised error
- * @param context context of the error
- * @returns a dictionary with the error details for the ErrorOrchestator
- */
- private getErrorOptions(error: unknown, context: string): UIErrorLog {
- return {
- context: context,
- level: UI_LOGGER_LEVELS.ERROR as UILogLevel,
- severity: UI_ERROR_SEVERITIES.BUSINESS as UIErrorSeverity,
- error: {
- error: error,
- message: error.message || error,
- title: error.name,
- },
- };
- }
-
- buildComplianceBadges(item) {
- const badgeList = [];
- const fields = ['pci_dss', 'gpg13', 'hipaa', 'gdpr', 'nist_800_53', 'tsc', 'mitre'];
- const buildBadge = field => {
- const idGenerator = () => {
- return (
- '_' +
- Math.random()
- .toString(36)
- .substr(2, 9)
- );
- };
-
- return (
-
- ev.stopPropagation()}
- onClickAriaLabel={field.toUpperCase()}
- style={{ margin: '1px 2px' }}
- >
- {field.toUpperCase()}
-
-
- );
- };
- try {
- for (const field of fields) {
- if (item[field].length) {
- badgeList.push(buildBadge(field));
- }
- }
- } catch (error) {}
-
- return {badgeList}
;
- }
-}
diff --git a/public/controllers/management/components/management/ruleset/rule-info.js b/public/controllers/management/components/management/ruleset/views/rule-info.tsx
similarity index 74%
rename from public/controllers/management/components/management/ruleset/rule-info.js
rename to public/controllers/management/components/management/ruleset/views/rule-info.tsx
index 355ffd164a..fe332e28ce 100644
--- a/public/controllers/management/components/management/ruleset/rule-info.js
+++ b/public/controllers/management/components/management/ruleset/views/rule-info.tsx
@@ -1,16 +1,14 @@
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
// Eui components
import {
EuiFlexGroup,
EuiFlexItem,
- EuiPanel,
- EuiPage,
- EuiButtonIcon,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
EuiTitle,
EuiToolTip,
EuiBadge,
EuiSpacer,
- EuiInMemoryTable,
EuiLink,
EuiAccordion,
EuiFlexGrid,
@@ -18,25 +16,16 @@ import {
EuiLoadingSpinner,
} from '@elastic/eui';
-import { connect } from 'react-redux';
-import { WzRequest } from '../../../../../react-services/wz-request';
+import { WzRequest } from '../../../../../../react-services/wz-request';
-import { RulesetHandler, RulesetResources } from './utils/ruleset-handler';
+import { ResourcesHandler, ResourcesConstants } from '../../common/resources-handler';
+import WzTextWithTooltipTruncated from '../../../../../../components/common/wz-text-with-tooltip-if-truncated';
+import { UI_ERROR_SEVERITIES } from '../../../../../../react-services/error-orchestrator/types';
+import { UI_LOGGER_LEVELS } from '../../../../../../../common/constants';
+import { TableWzAPI } from '../../../../../../components/common/tables';
+import { getErrorOrchestrator } from '../../../../../../react-services/common-services';
-import {
- updateFileContent,
- cleanFileContent,
- cleanInfo,
- updateFilters,
- cleanFilters,
-} from '../../../../../redux/actions/rulesetActions';
-
-import WzTextWithTooltipTruncated from '../../../../../components/common/wz-text-with-tooltip-if-truncated';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-
-class WzRuleInfo extends Component {
+export default class WzRuleInfo extends Component {
constructor(props) {
super(props);
this.complianceEquivalences = {
@@ -56,13 +45,15 @@ class WzRuleInfo extends Component {
mitreTechniques: [],
mitreRuleId: '',
mitreIds: [],
+ currentRuleInfo: {},
+ isLoading: true
};
- this.rulesetHandler = new RulesetHandler(RulesetResources.RULES);
+ this.resourcesHandler = new ResourcesHandler(ResourcesConstants.RULES);
- const handleFileClick = async (event, {filename, relative_dirname}) => {
+ const handleFileClick = async (event, { filename, relative_dirname }) => {
event.stopPropagation();
try {
- const result = await this.rulesetHandler.getFileContent(filename);
+ const result = await this.resourcesHandler.getFileContent(filename);
const file = {
name: filename,
content: result,
@@ -151,11 +142,67 @@ class WzRuleInfo extends Component {
];
}
- componentDidMount() {
+ async componentDidMount() {
document.body.scrollTop = 0; // For Safari
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
+
+ this.setState({
+ currentRuleId: this.props.item,
+ isLoading: true,
+ mitreLoading: true,
+ });
+ }
+
+ async componentDidUpdate(prevProps, prevState) {
+ if (prevState.currentRuleId !== this.state.currentRuleId)
+ await this.loadRule()
}
+ async loadRule() {
+ const { currentRuleId, mitreRuleId } = this.state;
+ try {
+ let mitreState = {};
+ const result = await this.resourcesHandler.getResource({
+ params: {
+ rule_ids: currentRuleId,
+ },
+ });
+ const currentRule = result?.data?.affected_items?.length ? result.data.affected_items[0] : {};
+
+ const compliance = this.buildCompliance(currentRule);
+ if (compliance?.mitre?.length && currentRuleId !== mitreRuleId) {
+ mitreState = this.addMitreInformation(compliance.mitre, currentRuleId);
+ } else if (currentRuleId !== mitreRuleId) {
+ mitreState = {
+ mitreLoading: false,
+ mitreRuleId: currentRuleId,
+ mitreIds: [],
+ mitreTactics: [],
+ mitreTechniques: [],
+ }
+ }
+
+ this.setState({
+ currentRuleInfo: currentRule,
+ compliance: compliance,
+ isLoading: false,
+ ...mitreState
+ });
+ } catch (error) {
+ const options = {
+ context: `${WzRuleInfo.name}.handleFileClick`,
+ level: UI_LOGGER_LEVELS.ERROR,
+ severity: UI_ERROR_SEVERITIES.BUSINESS,
+ error: {
+ error: error,
+ message: `Error updating file content: ${error.message || error}`,
+ title: error.name || error,
+ },
+ };
+ getErrorOrchestrator().handleError(options);
+ }
+
+ }
/**
* Build an object with the compliance info about a rule
* @param {Object} ruleInfo
@@ -173,12 +220,9 @@ class WzRuleInfo extends Component {
const badgeList = [];
const fields = ['pci_dss', 'gpg13', 'hipaa', 'gdpr', 'nist_800_53', 'tsc', 'mitre'];
const buildBadge = (field) => {
- const idGenerator = () => {
- return '_' + Math.random().toString(36).substr(2, 9);
- };
-
+
return (
-
+
ev.stopPropagation()}
@@ -218,10 +262,13 @@ class WzRuleInfo extends Component {
* Clean the existing filters and sets the new ones and back to the previous section
*/
setNewFiltersAndBack(filters) {
- window.location.href = window.location.href.replace(new RegExp('redirectRule=' + '[^&]*'), '');
+ window.history.pushState("",
+ window.document.title,
+ window.location.href.replace(new RegExp('&redirectRule=' + '[^&]*'), '')
+ );
this.props.cleanFilters();
- this.props.updateFilters(filters);
- this.props.cleanInfo();
+ this.props.onFiltersChange(filters);
+ this.props.closeFlyout();
}
/**
@@ -231,7 +278,7 @@ class WzRuleInfo extends Component {
* @param {String} file
* @param {String} path
*/
- renderInfo(id, level, file, path, groups) {
+ renderInfo(id = '', level = '', file = '', path = '', groups = []) {
return (
@@ -340,16 +387,16 @@ class WzRuleInfo extends Component {
* Render a list with the details
* @param {Array} details
*/
- renderDetails(details) {
+ renderDetails(details = {}) {
const detailsToRender = [];
- const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
+
// Exclude group key of details
Object.keys(details)
.filter((key) => key !== 'group')
.forEach((key) => {
detailsToRender.push(
- {capitalize(key)}
+ {key}
{details[key] === '' ? 'true' : this.getFormattedDetails(details[key])}
);
@@ -404,8 +451,9 @@ class WzRuleInfo extends Component {
}
async addMitreInformation(compliance, currentRuleId) {
+ let newMitreState = {};
try {
- this.setState({ mitreLoading: true, mitreRuleId: currentRuleId });
+ Object.assign(newMitreState, { mitreRuleId: currentRuleId });
const mitreName = [];
const mitreIds = [];
const mitreTactics = await Promise.all(
@@ -425,8 +473,7 @@ class WzRuleInfo extends Component {
if (mitreTactics.length) {
let removeDuplicates = (arr) => arr.filter((v, i) => arr.indexOf(v) === i);
const uniqueTactics = removeDuplicates(mitreTactics.flat());
- this.setState({
- mitreLoading: false,
+ Object.assign(newMitreState, {
mitreRuleId: currentRuleId,
mitreIds,
mitreTactics: uniqueTactics,
@@ -434,7 +481,7 @@ class WzRuleInfo extends Component {
});
}
} catch (error) {
- this.setState({ mitreLoading: false });
+ Object.assign(newMitreState, { mitreLoading: false });
const options = {
context: `${WzRuleInfo.name}.addMitreInformation`,
level: UI_LOGGER_LEVELS.ERROR,
@@ -447,6 +494,7 @@ class WzRuleInfo extends Component {
};
getErrorOrchestrator().handleError(options);
}
+ return newMitreState;
}
/**
@@ -458,17 +506,9 @@ class WzRuleInfo extends Component {
this.state && this.state.currentRuleId
? this.state.currentRuleId
: this.props.state.ruleInfo.current;
- if (compliance.mitre && compliance.mitre.length && currentRuleId !== this.state.mitreRuleId) {
- this.addMitreInformation(compliance.mitre, currentRuleId);
- } else if (currentRuleId !== this.state.mitreRuleId) {
- this.setState({
- mitreLoading: false,
- mitreRuleId: currentRuleId,
- mitreIds: [],
- mitreTactics: [],
- mitreTechniques: [],
- });
- }
+
+
+
const listCompliance = [];
if (compliance.mitre) delete compliance.mitre;
const keys = Object.keys(compliance);
@@ -543,11 +583,16 @@ class WzRuleInfo extends Component {
* @param {Number} ruleId
*/
changeBetweenRules(ruleId) {
+ // Prevent reloading the same rule
+ if (this.state.currentRuleId == ruleId) {
+ return;
+ }
+
window.location.href = window.location.href.replace(
new RegExp('redirectRule=' + '[^&]*'),
`redirectRule=${ruleId}`
);
- this.setState({ currentRuleId: ruleId });
+ this.setState({ currentRuleId: ruleId, isLoading: true });
}
/**
@@ -568,72 +613,50 @@ class WzRuleInfo extends Component {
return value;
}
- render() {
- const { ruleInfo, isLoading } = this.props.state;
- const currentRuleId =
- this.state && this.state.currentRuleId ? this.state.currentRuleId : ruleInfo.current;
- const rules = ruleInfo.affected_items;
- const currentRuleArr = rules.filter((r) => {
- return r.id === currentRuleId;
- });
- const currentRuleInfo = currentRuleArr[0];
- const { description, details, filename, relative_dirname, level, id, groups } = currentRuleInfo;
- const compliance = this.buildCompliance(currentRuleInfo);
- const columns = this.columns;
-
- const onClickRow = (item) => {
- return {
- onClick: () => {
- this.changeBetweenRules(item.id);
- },
- };
+ onClickRow = (item) => {
+ return {
+ onClick: () => {
+ this.changeBetweenRules(item.id);
+ },
};
+ };
+
+ render() {
+ const { description, details, filename, relative_dirname, level, id, groups } = this.state.currentRuleInfo;
+ const compliance = this.buildCompliance(this.state.currentRuleInfo);
return (
-
-
-
- {/* Rule description name */}
-
-
-
-
-
- {
- window.location.href = window.location.href.replace(
- new RegExp('redirectRule=' + '[^&]*'),
- ''
- );
- this.props.cleanInfo();
- }}
- />
-
- {
+ <>
+
+
+
+
+
+ {
+ description && (
- }
-
-
-
-
-
- View alerts of this Rule
+ />)
+ }
+
+
+
+
+
+ View alerts of this Rule
-
-
- {/* Cards */}
-
+
+
+
+
+
+
+ {/* Cards */}
{/* General info */}
@@ -646,10 +669,12 @@ class WzRuleInfo extends Component {
}
paddingSize="none"
initialIsOpen={true}
+ isLoading={this.state.isLoading}
+ isLoadingMessage={''}
>
-
+
{this.renderInfo(id, level, filename, relative_dirname, groups)}
-
+
@@ -665,13 +690,15 @@ class WzRuleInfo extends Component {
}
paddingSize="none"
initialIsOpen={true}
+ isLoading={this.state.isLoading}
+ isLoadingMessage={''}
>
- {this.renderDetails(details)}
+ {this.renderDetails(details)}
{/* Compliance */}
- {Object.keys(compliance).length > 0 && (
+ {compliance && Object.keys(compliance).length > 0 && (
-
+
{this.renderCompliance(compliance)}
-
+
@@ -701,50 +730,33 @@ class WzRuleInfo extends Component {
Related rules
}
+ isLoading={this.state.isLoading}
+ isLoadingMessage={''}
paddingSize="none"
initialIsOpen={true}
>
-
+
-
+ {this.state.currentRuleInfo?.filename &&
+
+ }
-
+
-
-
-
-
+
+
+
+ >
);
}
}
-
-const mapStateToProps = (state) => {
- return {
- state: state.rulesetReducers,
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- updateFileContent: (content) => dispatch(updateFileContent(content)),
- cleanFileContent: () => dispatch(cleanFileContent()),
- updateFilters: (filters) => dispatch(updateFilters(filters)),
- cleanFilters: () => dispatch(cleanFilters()),
- cleanInfo: () => dispatch(cleanInfo()),
- };
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(WzRuleInfo);
diff --git a/public/controllers/management/components/management/ruleset/views/ruleset-overview.tsx b/public/controllers/management/components/management/ruleset/views/ruleset-overview.tsx
new file mode 100644
index 0000000000..62908569d2
--- /dev/null
+++ b/public/controllers/management/components/management/ruleset/views/ruleset-overview.tsx
@@ -0,0 +1,69 @@
+import React, { useState } from 'react';
+
+// Eui components
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPanel,
+ EuiPage,
+ EuiSpacer
+} from '@elastic/eui';
+
+// Wazuh components
+import { withUserAuthorizationPrompt, withGlobalBreadcrumb } from '../../../../../../components/common/hocs';
+import { compose } from 'redux';
+import { resourceDictionary } from '../../common/resources-handler';
+import { SECTION_RULES_NAME, SECTION_RULES_KEY } from '../../common/constants';
+import RulesetTable from '../components/ruleset-table';
+import '../../common/layout-overview.scss';
+import WzRestartClusterManagerCallout from '../../../../../../components/common/restart-cluster-manager-callout';
+
+
+function WzRulesetOverview(props) {
+
+ const [showWarningRestart, setShowWarningRestart] = useState(false);
+
+ const updateRestartManagers = (showWarningRestart) => {
+ setShowWarningRestart(showWarningRestart);
+ }
+
+
+ const { clusterStatus } = props;
+ return
+
+ {showWarningRestart && (
+ <>
+
+ updateRestartManagers(false)}
+ onRestartedError={() => updateRestartManagers(true)}
+ />
+
+ >
+ )}
+
+
+
+ updateRestartManagers(showWarningRestart)}
+ />
+
+
+
+ ;
+}
+
+export default compose(
+ withGlobalBreadcrumb(props => {
+ return [
+ { text: '' },
+ { text: 'Management', href: '#/manager' },
+ { text: SECTION_RULES_NAME}
+ ];
+ }),
+ withUserAuthorizationPrompt((props) => [
+ { action: `${SECTION_RULES_KEY}:read`, resource: resourceDictionary[SECTION_RULES_KEY].permissionResource('*') }
+ ])
+)(WzRulesetOverview);
diff --git a/public/controllers/management/components/upload-files.js b/public/controllers/management/components/upload-files.js
index 3b4c864f7d..0632aa6492 100644
--- a/public/controllers/management/components/upload-files.js
+++ b/public/controllers/management/components/upload-files.js
@@ -30,7 +30,9 @@ import {
import { getToasts } from '../../../kibana-services';
import { WzButtonPermissions } from '../../../components/common/permissions/button';
-import { resourceDictionary, RulesetResources } from './management/ruleset/utils/ruleset-handler';
+import { resourceDictionary, ResourcesConstants } from './management/common/resources-handler';
+
+
export class UploadFiles extends Component {
constructor(props) {
super(props);
@@ -198,7 +200,7 @@ export class UploadFiles extends Component {
*/
checkValidFileExtensions() {
const resource = this.props.resource;
- if ([RulesetResources.RULES, RulesetResources.DECODERS].includes(resource)) {
+ if ([ResourcesConstants.RULES, ResourcesConstants.DECODERS].includes(resource)) {
const result = Object.keys(this.state.files).filter(item => {
const file = this.state.files[item].name;
return file.substr(file.length - 4) !== '.xml';
diff --git a/public/controllers/management/management.js b/public/controllers/management/management.js
index 3629b75f10..59c9dab970 100644
--- a/public/controllers/management/management.js
+++ b/public/controllers/management/management.js
@@ -16,9 +16,9 @@ import { WzRequest } from '../../react-services/wz-request';
import { ErrorHandler } from '../../react-services/error-handler';
import { ShareAgent } from '../../factories/share-agent';
import {
- RulesetHandler,
- RulesetResources,
-} from './components/management/ruleset/utils/ruleset-handler';
+ ResourcesHandler,
+ ResourcesConstants
+} from './components/management/common/resources-handler';
import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types';
import { UI_LOGGER_LEVELS } from '../../../common/constants';
@@ -47,7 +47,7 @@ export class ManagementController {
this.currentGroup = false;
this.logtestOpened = false;
this.uploadOpened = false;
- this.rulesetTab = RulesetResources.RULES;
+ this.rulesetTab = ResourcesConstants.RULES;
this.$scope.$on('setCurrentGroup', (ev, params) => {
this.currentGroup = (params || {}).currentGroup || false;
@@ -490,12 +490,12 @@ export class ManagementController {
try {
this.errors = false;
this.results = [];
- const rulesetHandler = new RulesetHandler(resource);
+ const resourcesHandler = new ResourcesHandler(resource);
for (let idx in files) {
const { file, content } = files[idx];
try {
- await rulesetHandler.updateFile(file, content, true); // True does not overwrite the file
+ await resourcesHandler.updateFile(file, content, true); // True does not overwrite the file
this.results.push({
index: idx,
uploaded: true,
diff --git a/public/redux/actions/rulesetActions.js b/public/redux/actions/rulesetActions.js
deleted file mode 100644
index 168f74a21c..0000000000
--- a/public/redux/actions/rulesetActions.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-
-/**
- * Update the ruleset section
- * @param {String} section
- */
-export const updateRulesetSection = section => {
- return {
- type: 'UPDATE_RULESET_SECTION',
- section
- };
-};
-
-/**
- * Update the files content
- * @param {String} content
- */
-export const updateFileContent = content => {
- return {
- type: 'UPDATE_FILE_CONTENT',
- content: content
- };
-};
-
-/**
- * Toggle the modal confirm of the ruleset table
- * @param {Boolean} showModal
- */
-export const updateShowModal = showModal => {
- return {
- type: 'UPDATE_SHOW_MODAL',
- showModal: showModal
- };
-};
-
-/**
- * Update the list of items to remove
- * @param {Array} itemList
- */
-export const updateListItemsForRemove = itemList => {
- return {
- type: 'UPDATE_LIST_ITEMS_FOR_REMOVE',
- itemList: itemList
- };
-};
-
-export const updateSortField = sortField => {
- return {
- type: 'UPDATE_SORT_FIELD',
- sortField: sortField
- };
-};
-
-export const updateSortDirection = sortDirection => {
- return {
- type: 'UPDATE_SORT_DIRECTION',
- sortDirection: sortDirection
- };
-};
-
-export const updateDefaultItems = defaultItems => {
- return {
- type: 'UPDATE_DEFAULT_ITEMS',
- defaultItems: defaultItems
- };
-};
-
-/**
- * Update the lists content
- * @param {String} content
- */
-export const updateListContent = content => {
- return {
- type: 'UPDATE_LIST_CONTENT',
- content: content
- };
-};
-
-/**
- * Update the loading status
- * @param {Boolean} loading
- */
-export const updateLoadingStatus = loading => {
- return {
- type: 'UPDATE_LOADING_STATUS',
- status: loading
- };
-};
-
-/**
- * Reset the ruleset store
- */
-export const resetRuleset = () => {
- return {
- type: 'RESET'
- };
-};
-
-/**
- * Toggle show files
- * @param {Boolean} status
- */
-export const toggleShowFiles = status => {
- return {
- type: 'TOGGLE_SHOW_FILES',
- status: status
- };
-};
-
-/**
- * Update the rule info
- * @param {String} info
- */
-export const updateRuleInfo = info => {
- return {
- type: 'UPDATE_RULE_INFO',
- info: info
- };
-};
-
-/**
- * Update the decoder info
- * @param {String} info
- */
-export const updateDecoderInfo = info => {
- return {
- type: 'UPDATE_DECODER_INFO',
- info: info
- };
-};
-
-/**
- * Toggle the updating of the table
- * @param {Boolean} isProcessing
- */
-export const updateIsProcessing = isProcessing => {
- return {
- type: 'UPDATE_IS_PROCESSING',
- isProcessing: isProcessing
- };
-};
-
-/**
- * Set the page index value of the table
- * @param {Number} pageIndex
- */
-export const updatePageIndex = pageIndex => {
- return {
- type: 'UPDATE_PAGE_INDEX',
- pageIndex: pageIndex
- };
-};
-
-/**
- * Update the filters
- * @param {string} filters
- */
-export const updateFilters = filters => {
- return {
- type: 'UPDATE_RULE_FILTERS',
- filters: filters
- };
-};
-
-export const cleanFilters = () => {
- return {
- type: 'CLEAN_FILTERS'
- };
-};
-
-export const cleanInfo = () => {
- return {
- type: 'CLEAN_INFO'
- };
-};
-
-export const cleanFileContent = () => {
- return {
- type: 'CLEAN_CONTENT'
- };
-};
-
-export const updteAddingRulesetFile = content => {
- return {
- type: 'UPDATE_ADDING_RULESET_FILE',
- content: content
- };
-};
-
-export const updateError = error => {
- return {
- type: 'UPDATE_ERROR',
- error: error
- };
-};
diff --git a/public/redux/reducers/rootReducers.js b/public/redux/reducers/rootReducers.js
index c85271cf46..34be93893a 100644
--- a/public/redux/reducers/rootReducers.js
+++ b/public/redux/reducers/rootReducers.js
@@ -11,7 +11,6 @@
*/
import { combineReducers } from 'redux';
-import rulesetReducers from './rulesetReducers';
import groupsReducers from './groupsReducers';
import statusReducers from './statusReducers';
import reportingReducers from './reportingReducers';
@@ -25,7 +24,6 @@ import toolsReducers from './toolsReducers';
import appConfig from './appConfigReducers';
export default combineReducers({
- rulesetReducers,
groupsReducers,
statusReducers,
reportingReducers,
diff --git a/public/redux/reducers/rulesetReducers.js b/public/redux/reducers/rulesetReducers.js
deleted file mode 100644
index 560b38fe3f..0000000000
--- a/public/redux/reducers/rulesetReducers.js
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Wazuh app - React component for registering agents.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-
-const initialState = {
- addingRulesetFile: false,
- decoderInfo: false,
- error: false,
- fileContent: false,
- filters: [],
- isLoading: false,
- isProcessing: false,
- itemList: [],
- items: [],
- listInfo: false,
- pageIndex: 0,
- ruleInfo: false,
- section: '',
- showingFiles: false,
- showModal: false,
- sortDirection: 'asc',
- sortField: 'id',
- defaultItems: []
-};
-
-const rulesetReducers = (state = initialState, action) => {
- switch (action.type) {
- case 'CLEAN_CONTENT':
- return Object.assign({}, state, { fileContent: false, error: false });
- case 'CLEAN_FILTERS':
- return Object.assign({}, state, { filters: [] });
- case 'CLEAN_INFO':
- return Object.assign({}, state, {
- decoderInfo: false,
- ruleInfo: false,
- listInfo: false,
- fileContent: false,
- addingRulesetFile: false,
- error: false
- });
- case 'RESET':
- return initialState;
- case 'TOGGLE_SHOW_FILES':
- return Object.assign({}, state, {
- showingFiles: action.status,
- error: false
- });
- case 'UPDATE_ADDING_RULESET_FILE':
- return Object.assign({}, state, {
- addingRulesetFile: action.content,
- error: false
- });
- case 'UPDATE_DECODER_INFO':
- return Object.assign({}, state, {
- decoderInfo: action.info,
- ruleInfo: false,
- listInfo: false,
- error: false
- });
- case 'UPDATE_DEFAULT_ITEMS':
- return Object.assign({}, state, {
- defaultItems: action.defaultItems,
- error: false
- });
- case 'UPDATE_ERROR':
- return Object.assign({}, state, { error: action.error });
- case 'UPDATE_FILE_CONTENT':
- return Object.assign({}, state, {
- fileContent: action.content,
- decoderInfo: false,
- ruleInfo: false,
- listInfo: false,
- error: false
- });
- case 'UPDATE_RULE_FILTERS':
- return Object.assign({}, state, {
- filters: action.filters,
- isProcessing: true,
- error: false
- });
- case 'UPDATE_IS_PROCESSING':
- return Object.assign({}, state, {
- isProcessing: action.isProcessing,
- ruleInfo: false,
- listInfo: false,
- error: false
- });
- case 'UPDATE_ITEMS':
- return Object.assign({}, state, { items: action.items, error: false });
- case 'REMOVE_ITEMS':
- return Object.assign({}, state, { items: [], error: false });
- case 'UPDATE_LIST_CONTENT':
- return Object.assign({}, state, {
- fileContent: false,
- decoderInfo: false,
- ruleInfo: false,
- listInfo: action.content,
- error: false
- });
- case 'UPDATE_LIST_ITEMS_FOR_REMOVE':
- return Object.assign({}, state, {
- itemList: action.itemList,
- error: false
- });
- case 'UPDATE_LOADING_STATUS':
- return Object.assign({}, state, {
- isLoading: action.status,
- error: false
- });
- case 'UPDATE_PAGE_INDEX':
- return Object.assign({}, state, {
- pageIndex: action.pageIndex,
- ruleInfo: false,
- listInfo: false,
- error: false
- });
- case 'UPDATE_RULE_INFO':
- return Object.assign({}, state, {
- ruleInfo: action.info,
- decoderInfo: false,
- listInfo: false,
- error: false
- });
- case 'UPDATE_RULESET_SECTION':
- return Object.assign({}, state, {
- section: action.section,
- showingFiles: false,
- error: false
- });
- case 'UPDATE_SHOW_MODAL':
- return Object.assign({}, state, {
- showModal: action.showModal,
- error: false
- });
- case 'UPDATE_SORT_DIRECTION':
- return Object.assign({}, state, {
- sortDirection: action.sortDirection,
- error: false
- });
- case 'UPDATE_SORT_FIELD':
- return Object.assign({}, state, {
- sortField: action.sortField,
- error: false
- });
- default:
- return state;
- }
-};
-
-export default rulesetReducers;
diff --git a/public/styles/common.scss b/public/styles/common.scss
index 47c4d2331b..3f81189854 100644
--- a/public/styles/common.scss
+++ b/public/styles/common.scss
@@ -1717,31 +1717,39 @@ iframe.width-changed {
text-align: center;
}
-.withUserLogged-logo{
+.withUserLogged-logo {
width: 140px;
margin: 0 auto;
}
-.withUserLogged-loader{
+.withUserLogged-loader {
width: 20px;
margin: 0 auto;
}
-.custom-charts-bar span.euiLoadingChart__bar{
+.custom-charts-bar span.euiLoadingChart__bar {
width: 15px;
}
-.wz-width-100{
+.wz-width-100 {
width: 100%;
}
-.wz-user-select-none{
+.wz-txt-capitalize {
+ text-transform: capitalize;
+}
+
+.wz-user-select-none {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
+.wz-flex-basis-auto {
+ flex-basis: auto !important;
+}
+
.wz-euiCard-no-title{
.euiCard__title,
.euiCard__description
@@ -1759,11 +1767,11 @@ iframe.width-changed {
/* Custom Searchbar styles */
-.application .filters-search-bar .globalQueryBar, .app-container .filters-search-bar .globalQueryBar{
+.application .filters-search-bar .globalQueryBar, .app-container .filters-search-bar .globalQueryBar {
padding: 0 !important;
}
-@media only screen and (max-width: 960px){
+@media only screen and (max-width: 960px) {
.custom-kbn-search-bar .euiSuperUpdateButton .euiSuperUpdateButton__text {
display: none;
}
@@ -1773,7 +1781,7 @@ iframe.width-changed {
}
}
-@media only screen and (max-width: 767px){
+@media only screen and (max-width: 767px) {
.header-global-wrapper + .app-wrapper:not(.hidden-chrome) {
left: 0!important;
}