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; }