@@ -405,110 +159,6 @@ export const AgentsTable = withErrorBoundary(
);
}
- reloadAgent = () => {
- this._isMount &&
- this.setState({
- isLoading: true,
- });
- this.props.reload();
- };
-
- downloadCsv = () => {
- const filters = this.buildFilter();
- const formatedFilters = Object.keys(filters)
- .filter(field => !['limit', 'offset', 'sort'].includes(field))
- .map(field => ({ name: field, value: filters[field] }));
- this.props.downloadCsv(formatedFilters);
- };
-
- openColumnsFilter = () => {
- this.setState({
- isFilterColumnOpen: !this.state.isFilterColumnOpen,
- });
- };
-
- formattedButton() {
- return (
- <>
-
-
- Export formatted
-
-
-
-
-
-
-
-
-
- >
- );
- }
-
- showToast = (color, title, text, time) => {
- getToasts().add({
- color: color,
- title: title,
- text: text,
- toastLifeTimeMs: time,
- });
- };
-
- callOutRender() {
- const { selectedItems, pageSize, allSelected, totalItems } = this.state;
-
- if (selectedItems.length === 0) {
- return;
- } else if (selectedItems.length === pageSize) {
- return (
-
-
-
-
-
- {
- this._isMount &&
- this.setState(prevState => ({
- allSelected: !prevState.allSelected,
- }));
- }}
- >
- {allSelected
- ? `Clear all agents selection (${totalItems})`
- : `Select all agents (${totalItems})`}
-
-
-
-
-
-
- );
- }
- }
-
- getTableColumnsSelected() {
- return (
- JSON.parse(window.localStorage.getItem('columnsSelectedTableAgent')) ||
- []
- );
- }
-
- setTableColumnsSelected(data) {
- window.localStorage.setItem(
- 'columnsSelectedTableAgent',
- JSON.stringify(data),
- );
- }
-
// Columns with the property truncateText: true won't wrap the text
// This is added to prevent the wrap because of the table-layout: auto
defaultColumns = [
@@ -517,18 +167,21 @@ export const AgentsTable = withErrorBoundary(
name: 'ID',
sortable: true,
show: true,
+ searchable: true,
},
{
field: 'name',
name: 'Name',
sortable: true,
show: true,
+ searchable: true,
},
{
field: 'ip',
name: 'IP address',
sortable: true,
show: true,
+ searchable: true,
},
{
field: 'group',
@@ -536,37 +189,64 @@ export const AgentsTable = withErrorBoundary(
sortable: true,
show: true,
render: groups => (groups !== '-' ? this.renderGroups(groups) : '-'),
+ searchable: true,
},
{
- field: 'os_name',
+ field: 'os.name,os.version',
+ composeField: ['os.name', 'os.version'],
name: 'Operating system',
sortable: true,
show: true,
- render: this.addIconPlatformRender,
+ render: (field, agentData) => this.addIconPlatformRender(agentData),
+ searchable: true,
},
{
field: 'node_name',
name: 'Cluster node',
sortable: true,
show: true,
+ searchable: true,
},
{
field: 'version',
name: 'Version',
sortable: true,
show: true,
+ searchable: true,
},
{
field: 'dateAdd',
- name: 'Registration date',
+ name: (
+
+ Registration date{' '}
+
+
+ ),
sortable: true,
show: false,
+ searchable: false,
},
{
field: 'lastKeepAlive',
- name: 'Last keep alive',
+ name: (
+
+ Last keep alive{' '}
+
+
+ ),
sortable: true,
show: false,
+ searchable: false,
},
{
field: 'status',
@@ -580,6 +260,7 @@ export const AgentsTable = withErrorBoundary(
labelProps={{ className: 'hide-agent-status' }}
/>
),
+ searchable: true,
},
{
field: 'group_config_status',
@@ -587,6 +268,7 @@ export const AgentsTable = withErrorBoundary(
sortable: true,
show: false,
render: synced =>
,
+ searchable: true,
},
{
align: 'right',
@@ -594,133 +276,11 @@ export const AgentsTable = withErrorBoundary(
field: 'actions',
name: 'Actions',
show: true,
- render: agent => this.actionButtonsRender(agent),
+ render: (field, agentData) => this.actionButtonsRender(agentData),
+ searchable: false,
},
];
- columns() {
- const selectedColumns = this.getTableColumnsSelected();
-
- if (selectedColumns.length != 0) {
- const newSelectedColumns = [];
- selectedColumns.forEach(item => {
- if (item.show) {
- const column = this.defaultColumns.find(
- column => column.field === item.field,
- );
- newSelectedColumns.push(column);
- }
- });
- return newSelectedColumns;
- } else {
- const fieldColumns = this.defaultColumns.map(item => {
- return {
- field: item.field,
- name: item.name,
- show: item.show,
- };
- });
- this.setTableColumnsSelected(fieldColumns);
- return fieldColumns;
- }
- }
-
- headRender() {
- const formattedButton = this.formattedButton();
- return (
-
-
-
-
-
- {!!this.state.totalItems && (
-
- Agents ({this.state.totalItems})
-
- )}
-
-
-
-
- this.props.addingNewAgent()}
- >
- Deploy new agent
-
-
- {formattedButton}
-
-
-
- );
- }
-
- filterBarRender() {
- return (
-
-
-
- this.setState({ filters, pageIndex: 0 })
- }
- placeholder='Filter or search agent'
- />
-
-
- this.reloadAgents()}
- >
- Refresh
-
-
-
- );
- }
-
- selectColumnsRender() {
- const columnsSelected = this.getTableColumnsSelected();
-
- const onChange = optionId => {
- let item = columnsSelected.find(item => item.field === optionId);
- item.show = !item.show;
- this.setTableColumnsSelected(columnsSelected);
- this.forceUpdate();
- };
-
- const options = () => {
- return columnsSelected.map(item => {
- return {
- id: item.field,
- label: item.name,
- checked: item.show,
- };
- });
- };
-
- return this.state.isFilterColumnOpen ? (
-
-
-
-
-
- ) : (
- ''
- );
- }
-
tableRender() {
const getRowProps = item => {
const { id } = item;
@@ -746,50 +306,201 @@ export const AgentsTable = withErrorBoundary(
};
};
- const {
- pageIndex,
- pageSize,
- totalItems,
- agents,
- sortField,
- sortDirection,
- isLoading,
- } = this.state;
- const columns = this.columns();
- const pagination =
- totalItems > 15
- ? {
- pageIndex: pageIndex,
- pageSize: pageSize,
- totalItemCount: totalItems,
- pageSizeOptions: [15, 25, 50, 100],
- }
- : false;
- const sorting = {
- sort: {
- field: sortField,
- direction: sortDirection,
- },
- };
-
// The EuiBasicTable tableLayout is set to "auto" to improve the use of empty space in the component.
// Previously the tableLayout is set to "fixed" with percentage width for each column, but the use of space was not optimal.
// Important: If all the columns have the truncateText property set to true, the table cannot adjust properly when the viewport size is small.
return (
- this.props.addingNewAgent()}
+ >
+ Deploy new agent
+ ,
+ ]}
+ endpoint='/agents'
+ tableColumns={this.defaultColumns}
+ tableInitialSortingField='id'
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ reload={this.state.reloadTable}
+ mapResponseItem={item => {
+ return {
+ ...item,
+ ...(item.ip ? { ip: item.ip } : { ip: '-' }),
+ ...(typeof item.dateAdd === 'string'
+ ? { dateAdd: formatUIDate(item.dateAdd) }
+ : { dateAdd: '-' }),
+ ...(typeof item.lastKeepAlive === 'string'
+ ? { lastKeepAlive: formatUIDate(item.lastKeepAlive) }
+ : { lastKeepAlive: '-' }),
+ ...(item.node_name !== 'unknown'
+ ? { node_name: item.node_name }
+ : { node_name: '-' }),
+ /*
+ The agent version contains the Wazuh word, this gets the string starting with
+ v
+ */
+ ...(typeof item.version === 'string'
+ ? { version: item.version.match(/(v\d.+)/)?.[1] }
+ : { version: '-' }),
+ };
+ }}
rowProps={getRowProps}
- cellProps={getCellProps}
- noItemsMessage='No agents found'
- {...(pagination && { pagination })}
+ filters={this.state.filters}
+ downloadCsv
+ showReload
+ showFieldSelector
+ searchTable
+ searchBarWQL={{
+ options: searchBarWQLOptions,
+ suggestions: {
+ field(currentValue) {
+ return [
+ {
+ label: 'dateAdd',
+ description: 'filter by registration date',
+ },
+ { label: 'id', description: 'filter by id' },
+ { label: 'ip', description: 'filter by IP address' },
+ { label: 'group', description: 'filter by group' },
+ {
+ label: 'group_config_status',
+ description: 'filter by group configuration status',
+ },
+ {
+ label: 'lastKeepAlive',
+ description: 'filter by last keep alive',
+ },
+ { label: 'manager', description: 'filter by manager' },
+ { label: 'name', description: 'filter by name' },
+ {
+ label: 'node_name',
+ description: 'filter by cluster name',
+ },
+ {
+ label: 'os.name',
+ description: 'filter by operating system name',
+ },
+ {
+ label: 'os.platform',
+ description: 'filter by operating platform',
+ },
+ {
+ label: 'os.version',
+ description: 'filter by operating system version',
+ },
+ { label: 'status', description: 'filter by status' },
+ { label: 'version', description: 'filter by version' },
+ ];
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ switch (field) {
+ case 'status':
+ return UI_ORDER_AGENT_STATUS.map(status => ({
+ label: status,
+ }));
+ case 'group_config_status':
+ return [
+ AGENT_SYNCED_STATUS.SYNCED,
+ AGENT_SYNCED_STATUS.NOT_SYNCED,
+ ].map(label => ({
+ label,
+ }));
+ default: {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ '/agents',
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? {
+ q: `${searchBarWQLOptions.implicitQuery.query}${searchBarWQLOptions.implicitQuery.conjunction}${field}~${currentValue}`,
+ }
+ : {
+ q: `${searchBarWQLOptions.implicitQuery.query}`,
+ }),
+ },
+ },
+ );
+ if (field === 'group') {
+ /* the group field is returned as an string[],
+ example: ['group1', 'group2']
+
+ Due the API request done to get the distinct values for the groups is
+ not returning the exepected values, as workaround, the values are
+ extracted in the frontend using the returned results.
+
+ This API request to get the distint values of groups doesn't
+ return the unique values for the groups, else the unique combination
+ of groups.
+ */
+ return response?.data?.data.affected_items
+ .map(item => getLodash(item, field))
+ .flat()
+ .filter(
+ (item, index, array) =>
+ array.indexOf(item) === index,
+ )
+ .sort()
+ .map(group => ({ label: group }));
+ }
+ return response?.data?.data.affected_items.map(
+ item => ({
+ label: getLodash(item, field),
+ }),
+ );
+ }
+ }
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ validate: {
+ value: ({ formattedValue, value: rawValue }, { field }) => {
+ const value = formattedValue ?? rawValue;
+ if (value) {
+ if (['dateAdd', 'lastKeepAlive'].includes(field)) {
+ return /^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}:\d{2}(.\d{1,6})?Z?)?$/.test(
+ value,
+ )
+ ? undefined
+ : `"${value}" is not a expected format. Valid formats: YYYY-MM-DD, YYYY-MM-DD HH:mm:ss, YYYY-MM-DDTHH:mm:ss, YYYY-MM-DDTHH:mm:ssZ.`;
+ }
+ }
+ },
+ },
+ }}
+ searchBarProps={{
+ buttonsRender: () => (
+ this.reloadAgents()}
+ >
+ Refresh
+
+ ),
+ }}
+ saveStateStorage={{
+ system: 'localStorage',
+ key: 'wz-agents-overview-table',
+ }}
+ tableProps={{
+ tableLayout: 'auto',
+ getCellProps,
+ }}
/>
@@ -797,25 +508,16 @@ export const AgentsTable = withErrorBoundary(
}
filterGroupBadge = group => {
- const { filters } = this.state;
- let auxFilters = filters.map(
- filter => filter.value.match(/group=(.*S?)/)[1],
- );
- if (filters.length > 0) {
- !auxFilters.includes(group)
- ? this.setState({
- filters: [...filters, { field: 'q', value: `group=${group}` }],
- })
- : false;
- } else {
- this.setState({
- filters: [...filters, { field: 'q', value: `group=${group}` }],
- });
- }
+ this.setState({
+ filters: {
+ default: { q: 'id!=000' },
+ q: `id!=000;group=${group}`,
+ },
+ });
};
renderGroups(groups) {
- return (
+ return Array.isArray(groups) ? (
- );
+ ) : undefined;
}
render() {
- const title = this.headRender();
- const filter = this.filterBarRender();
- const selectColumnsRender = this.selectColumnsRender();
const table = this.tableRender();
- const callOut = this.callOutRender();
- let renderPurgeModal, loadItems;
return (
- {filter}
-
-
- {title}
- {loadItems}
- {callOut}
- {selectColumnsRender}
- {table}
- {renderPurgeModal}
-
+ {table}
);
}
diff --git a/plugins/main/public/controllers/agent/wazuh-config/index.ts b/plugins/main/public/controllers/agent/wazuh-config/index.ts
index c8bafbbe2a..70b345bf8e 100644
--- a/plugins/main/public/controllers/agent/wazuh-config/index.ts
+++ b/plugins/main/public/controllers/agent/wazuh-config/index.ts
@@ -6,7 +6,7 @@ const architectureButtons = [
{
id: 'x86_64',
label: 'x86_64',
- default: true
+ default: true,
},
{
id: 'armhf',
@@ -26,7 +26,7 @@ const architectureButtonsWithPPC64LE = [
{
id: 'x86_64',
label: 'x86_64',
- default: true
+ default: true,
},
{
id: 'armhf',
@@ -54,7 +54,7 @@ const architectureButtonsWithPPC64LEAlpine = [
{
id: 'x86_64',
label: 'x86_64',
- default: true
+ default: true,
},
{
id: 'armhf',
@@ -85,7 +85,7 @@ const architecturei386Andx86_64 = [
{
id: 'x86_64',
label: 'x86_64',
- default: true
+ default: true,
},
];
@@ -93,7 +93,7 @@ const architectureButtonsSolaris = [
{
id: 'i386',
label: 'i386',
- default: true
+ default: true,
},
{
id: 'sparc',
@@ -138,7 +138,7 @@ const versionButtonAmazonLinux = [
{
id: 'amazonlinux2022',
label: 'Amazon Linux 2022',
- default: true
+ default: true,
},
];
@@ -154,7 +154,7 @@ const versionButtonsRedHat = [
{
id: 'redhat7',
label: 'Red Hat 7 +',
- default: true
+ default: true,
},
];
@@ -170,7 +170,7 @@ const versionButtonsCentos = [
{
id: 'centos7',
label: 'CentOS 7 +',
- default: true
+ default: true,
},
];
@@ -186,7 +186,7 @@ const versionButtonsDebian = [
{
id: 'debian9',
label: 'Debian 9 +',
- default: true
+ default: true,
},
];
@@ -205,7 +205,7 @@ const versionButtonsUbuntu = [
{
id: 'ubuntu15',
label: 'Ubuntu 15 +',
- default: true
+ default: true,
},
];
@@ -221,7 +221,7 @@ const versionButtonsWindows = [
{
id: 'windows7',
label: 'Windows 7 +',
- default: true
+ default: true,
},
];
@@ -233,7 +233,7 @@ const versionButtonsSuse = [
{
id: 'suse12',
label: 'SUSE 12',
- default: true
+ default: true,
},
];
@@ -259,7 +259,7 @@ const versionButtonsSolaris = [
{
id: 'solaris11',
label: 'Solaris 11',
- default: true
+ default: true,
},
];
@@ -285,7 +285,7 @@ const versionButtonsOracleLinux = [
{
id: 'oraclelinux6',
label: 'Oracle Linux 6 +',
- default: true
+ default: true,
},
];
diff --git a/plugins/main/public/controllers/management/components/management/cdblists/components/cdblists-table.tsx b/plugins/main/public/controllers/management/components/management/cdblists/components/cdblists-table.tsx
index d49792d817..11f4fc4854 100644
--- a/plugins/main/public/controllers/management/components/management/cdblists/components/cdblists-table.tsx
+++ b/plugins/main/public/controllers/management/components/management/cdblists/components/cdblists-table.tsx
@@ -13,12 +13,22 @@
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 {
+ 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 {
+ SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ UI_LOGGER_LEVELS,
+} from '../../../../../../../common/constants';
-import { SECTION_CDBLIST_SECTION, SECTION_CDBLIST_KEY } from '../../common/constants';
+import {
+ SECTION_CDBLIST_SECTION,
+ SECTION_CDBLIST_KEY,
+} from '../../common/constants';
import CDBListsColumns from './columns';
import { withUserPermissions } from '../../../../../../components/common/hocs/withUserPermissions';
@@ -29,43 +39,50 @@ import {
AddNewFileButton,
AddNewCdbListButton,
UploadFilesButton,
-} from '../../common/actions-buttons'
+} from '../../common/actions-buttons';
+import { WzRequest } from '../../../../../../react-services';
+
+const searchBarWQLOptions = {
+ searchTermFields: ['filename', 'relative_dirname'],
+ filterButtons: [
+ {
+ id: 'relative-dirname',
+ input: 'relative_dirname=etc/lists',
+ label: 'Custom lists',
+ },
+ ],
+};
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
+ defaultItems: [],
+ },
+ ...props,
}).columns;
const columns = cdblistsColumns[SECTION_CDBLIST_KEY];
return columns;
- }
+ };
/**
* Columns and Rows properties
*/
- const getRowProps = (item) => {
+ const getRowProps = item => {
const { id, name } = item;
- const getRequiredPermissions = (item) => {
+ const getRequiredPermissions = item => {
const { permissionResource } = resourceDictionary[SECTION_CDBLIST_KEY];
return [
{
@@ -80,17 +97,17 @@ function CDBListsTable(props) {
className: 'customRowClass',
onClick: !WzUserPermissions.checkMissingUserPermissions(
getRequiredPermissions(item),
- props.userPermissions
+ props.userPermissions,
)
- ? async (ev) => {
- const result = await resourcesHandler.getFileContent(item.filename);
- const file = {
- name: item.filename,
- content: result,
- path: item.relative_dirname,
- };
- updateListContent(file);
- }
+ ? async ev => {
+ const result = await resourcesHandler.getFileContent(item.filename);
+ const file = {
+ name: item.filename,
+ content: result,
+ path: item.relative_dirname,
+ };
+ updateListContent(file);
+ }
: undefined,
};
};
@@ -98,13 +115,13 @@ function CDBListsTable(props) {
/**
* Remove files method
*/
- const removeItems = async (items) => {
+ const removeItems = async items => {
try {
const results = items.map(async (item, i) => {
await resourcesHandler.deleteFile(item.filename || item.name);
});
- Promise.all(results).then((completed) => {
+ Promise.all(results).then(completed => {
setTableFootprint(Date.now());
getToasts().add({
color: 'success',
@@ -126,7 +143,7 @@ function CDBListsTable(props) {
};
getErrorOrchestrator().handleError(options);
}
- }
+ };
const { updateRestartClusterManager, updateListContent } = props;
const columns = getColumns();
@@ -153,38 +170,63 @@ function CDBListsTable(props) {
{ updateRestartClusterManager && updateRestartClusterManager() }}
+ onSuccess={() => {
+ updateRestartClusterManager && updateRestartClusterManager();
+ }}
/>,
];
-
-
return (
-
+
{
+ try {
+ const response = await WzRequest.apiReq('GET', '/lists', {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ },
+ });
+ return response?.data?.data.affected_items.map(item => ({
+ label: item[field],
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
endpoint={'/lists'}
isExpandable={true}
rowProps={getRowProps}
- downloadCsv={true}
- showReload={true}
- filters={filters}
- onFiltersChange={updateFilters}
+ downloadCsv
+ showReload
tablePageSizeOptions={[10, 25, 50, 100]}
/>
);
-
}
-
-export default compose(
- withUserPermissions
-)(CDBListsTable);
+export default compose(withUserPermissions)(CDBListsTable);
diff --git a/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts-labels.js b/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts-labels.js
index d0a9b448ae..49db124470 100644
--- a/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts-labels.js
+++ b/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts-labels.js
@@ -27,18 +27,18 @@ import { webDocumentationLink } from '../../../../../../../common/services/web_d
const columns = [
{ field: 'key', name: 'Label key' },
{ field: 'value', name: 'Label value' },
- { field: 'hidden', name: 'Hidden' }
+ { field: 'hidden', name: 'Hidden' },
];
const helpLinks = [
{
text: 'Agent labels',
- href: webDocumentationLink('user-manual/capabilities/labels.html')
+ href: webDocumentationLink('user-manual/agents/labels.html'),
},
{
text: 'Labels reference',
- href: webDocumentationLink('user-manual/reference/ossec-conf/labels.html')
- }
+ href: webDocumentationLink('user-manual/reference/ossec-conf/labels.html'),
+ },
];
class WzConfigurationAlertsLabels extends Component {
@@ -49,71 +49,34 @@ class WzConfigurationAlertsLabels extends Component {
const { currentConfig, agent, wazuhNotReadyYet } = this.props;
return (
- {currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ] &&
- isString(
- currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ]
- ) && (
+ {currentConfig['agent-labels'] &&
+ isString(currentConfig['agent-labels']) && (
)}
- {currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ] &&
- !isString(
- currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ]
- ) &&
- !hasSize(
- currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ].labels
- ) && }
+ {currentConfig['agent-labels'] &&
+ !isString(currentConfig['agent-labels']) &&
+ !hasSize(currentConfig['agent-labels'].labels) && (
+
+ )}
{wazuhNotReadyYet &&
- (!currentConfig ||
- !currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ]) && }
- {currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ] &&
- !isString(
- currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ]
- ) &&
- hasSize(
- currentConfig[
- agent && agent.id !== '000' ? 'agent-labels' : 'analysis-labels'
- ].labels
- ) ? (
+ (!currentConfig || !currentConfig['agent-labels']) && (
+
+ )}
+ {currentConfig['agent-labels'] &&
+ !isString(currentConfig['agent-labels']) &&
+ hasSize(currentConfig['agent-labels'].labels) ? (
) : null}
@@ -123,7 +86,7 @@ class WzConfigurationAlertsLabels extends Component {
}
const mapStateToProps = state => ({
- wazuhNotReadyYet: state.appStateReducers.wazuhNotReadyYet
+ wazuhNotReadyYet: state.appStateReducers.wazuhNotReadyYet,
});
export default connect(mapStateToProps)(WzConfigurationAlertsLabels);
@@ -132,15 +95,15 @@ const sectionsAgent = [{ component: 'agent', configuration: 'labels' }];
export const WzConfigurationAlertsLabelsAgent = compose(
connect(mapStateToProps),
- withWzConfig(sectionsAgent)
+ withWzConfig(sectionsAgent),
)(WzConfigurationAlertsLabels);
WzConfigurationAlertsLabels.propTypes = {
// currentConfig: PropTypes.object.isRequired,
- wazuhNotReadyYet: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
+ wazuhNotReadyYet: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
};
WzConfigurationAlertsLabelsAgent.propTypes = {
// currentConfig: PropTypes.object.isRequired,
- wazuhNotReadyYet: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
+ wazuhNotReadyYet: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
};
diff --git a/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts.js b/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts.js
index 704a38befc..c72e0b4cca 100644
--- a/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts.js
+++ b/plugins/main/public/controllers/management/components/management/configuration/alerts/alerts.js
@@ -14,7 +14,7 @@ import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import WzTabSelector, {
- WzTabSelectorTab
+ WzTabSelectorTab,
} from '../util-components/tab-selector';
import withWzConfig from '../util-hocs/wz-config';
import WzConfigurationAlertsGeneral from './alerts-general';
@@ -34,19 +34,19 @@ class WzConfigurationAlerts extends Component {
return (
-
+
-
+
-
+
-
+
-
+
@@ -57,22 +57,22 @@ class WzConfigurationAlerts extends Component {
const sections = [
{ component: 'analysis', configuration: 'alerts' },
- { component: 'analysis', configuration: 'labels' },
+ { component: 'agent', configuration: 'labels' },
{ component: 'mail', configuration: 'alerts' },
{ component: 'monitor', configuration: 'reports' },
- { component: 'csyslog', configuration: 'csyslog' }
+ { component: 'csyslog', configuration: 'csyslog' },
];
const mapStateToProps = state => ({
- wazuhNotReadyYet: state.appStateReducers.wazuhNotReadyYet
+ wazuhNotReadyYet: state.appStateReducers.wazuhNotReadyYet,
});
WzConfigurationAlerts.propTypes = {
// currentConfig: PropTypes.object.isRequired,
- wazuhNotReadyYet: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
+ wazuhNotReadyYet: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
};
export default compose(
withWzConfig(sections),
- connect(mapStateToProps)
+ connect(mapStateToProps),
)(WzConfigurationAlerts);
diff --git a/plugins/main/public/controllers/management/components/management/decoders/components/columns.tsx b/plugins/main/public/controllers/management/components/management/decoders/components/columns.tsx
index 32ee7ef1b2..727dc9b4fb 100644
--- a/plugins/main/public/controllers/management/components/management/decoders/components/columns.tsx
+++ b/plugins/main/public/controllers/management/components/management/decoders/components/columns.tsx
@@ -95,6 +95,12 @@ export default class DecodersColumns {
align: 'left',
sortable: true,
},
+ {
+ field: 'relative_dirname',
+ name: 'Path',
+ align: 'left',
+ sortable: true,
+ },
{
name: 'Actions',
align: 'left',
diff --git a/plugins/main/public/controllers/management/components/management/decoders/components/decoders-suggestions.ts b/plugins/main/public/controllers/management/components/management/decoders/components/decoders-suggestions.ts
index fbd4ed6999..d81f0e825c 100644
--- a/plugins/main/public/controllers/management/components/management/decoders/components/decoders-suggestions.ts
+++ b/plugins/main/public/controllers/management/components/management/decoders/components/decoders-suggestions.ts
@@ -1,44 +1,141 @@
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../../../common/constants';
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 });
- },
+const decodersItems = {
+ field(currentValue) {
+ return [
+ { label: 'details.order', description: 'filter by program name' },
+ { label: 'details.program_name', description: 'filter by program name' },
+ { label: 'filename', description: 'filter by filename' },
+ { label: 'name', description: 'filter by name' },
+ { label: 'relative_dirname', description: 'filter by relative path' },
+ ];
},
- {
- 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'
+ value: async (currentValue, { field }) => {
+ try {
+ switch (field) {
+ case 'details.order': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/decoders', {
+ params: filter,
+ });
+ return (
+ result?.data?.data?.affected_items
+ // There are some affected items that doesn't return any value for the selected property
+ ?.filter(item => typeof item?.details?.order === 'string')
+ ?.map(item => ({
+ label: item?.details?.order,
+ }))
+ );
+ }
+ case 'details.program_name': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/decoders', {
+ params: filter,
+ });
+ // FIX: this breaks the search bar component because returns a non-string value.
+ return result?.data?.data?.affected_items
+ ?.filter(item => typeof item?.details?.program_name === 'string')
+ .map(item => ({
+ label: item?.details?.program_name,
+ }));
+ }
+ case 'filename': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/decoders/files', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items?.map(item => ({
+ label: item[field],
+ }));
+ }
+ case 'name': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/decoders', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items?.map(item => ({
+ label: item[field],
+ }));
}
+ case 'relative_dirname': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/decoders', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items.map(item => ({
+ label: item[field],
+ }));
+ }
+ default: {
+ return [];
+ }
+ }
+ } catch (error) {
+ return [];
+ }
+ },
+};
+
+const decodersFiles = {
+ field(currentValue) {
+ return [
+ { label: 'filename', description: 'filter by filename' },
+ { label: 'relative_dirname', description: 'filter by relative dirname' },
+ ];
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/decoders/files', {
+ params: filter,
});
- return (((result || {}).data || {}).data || {}).affected_items[0].ruleset.decoder_dir;
+ return result?.data?.data?.affected_items?.map(item => ({
+ label: item[field],
+ }));
+ } catch (error) {
+ return [];
}
},
- {
- type: 'params',
- label: 'status',
- description: 'Filters the decoders by status.',
- values: ['enabled', 'disabled']
- }
-];
+};
const apiSuggestsItems = {
items: decodersItems,
- files: [],
+ files: decodersFiles,
};
-export default apiSuggestsItems;
\ No newline at end of file
+export default apiSuggestsItems;
diff --git a/plugins/main/public/controllers/management/components/management/decoders/components/decoders-table.tsx b/plugins/main/public/controllers/management/components/management/decoders/components/decoders-table.tsx
index 10930cc7bc..1cb105449b 100644
--- a/plugins/main/public/controllers/management/components/management/decoders/components/decoders-table.tsx
+++ b/plugins/main/public/controllers/management/components/management/decoders/components/decoders-table.tsx
@@ -39,41 +39,67 @@ import {
import apiSuggestsItems from './decoders-suggestions';
+const searchBarWQLOptions = {
+ searchTermFields: [
+ 'details.order',
+ 'details.program_name',
+ 'filename',
+ 'name',
+ 'relative_dirname',
+ ],
+ filterButtons: [
+ {
+ id: 'relative-dirname',
+ input: 'relative_dirname=etc/decoders',
+ label: 'Custom decoders',
+ },
+ ],
+};
+
+const searchBarWQLOptionsFiles = {
+ searchTermFields: ['filename', 'relative_dirname'],
+ filterButtons: [
+ {
+ id: 'relative-dirname',
+ input: 'relative_dirname=etc/rules',
+ label: 'Custom rules',
+ },
+ ],
+};
+
/***************************************
* Render tables
*/
const FilesTable = ({
actionButtons,
- buttonOptions,
columns,
searchBarSuggestions,
filters,
- updateFilters,
reload,
}) => (
);
const DecodersFlyoutTable = ({
actionButtons,
- buttonOptions,
columns,
searchBarSuggestions,
getRowProps,
@@ -88,20 +114,21 @@ const DecodersFlyoutTable = ({
<>
{isFlyoutVisible && (
@@ -134,15 +161,6 @@ export default compose(withUserPermissions)(function DecodersTable({
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);
};
@@ -278,17 +296,14 @@ export default compose(withUserPermissions)(function DecodersTable({
{showingFiles ? (
) : (
- this.setNewFiltersAndBack([
- { field: 'filename', value: file },
- ])
+ this.setNewFiltersAndBack({ q: `filename=${file}` })
}
>
{file}
@@ -157,9 +155,7 @@ export default class WzDecoderInfo extends Component {
- this.setNewFiltersAndBack([
- { field: 'relative_dirname', value: path },
- ])
+ this.setNewFiltersAndBack({ q: `relative_dirname=${path}` })
}
>
{path}
@@ -359,7 +355,7 @@ export default class WzDecoderInfo extends Component {
{currentDecoder?.filename && (
{
- if (!this.props.state.isProcessing) {
- this.props.updateLoadingStatus(false);
- clearInterval(this.refreshTimeoutId);
- }
- }, 100);
- }
showManageAgents() {
const { itemDetail } = this.props.state;
@@ -105,117 +42,6 @@ class WzGroupsActionButtonsAgents extends Component {
this.props.updateShowAddAgents(true);
}
- closePopover() {
- this.setState({
- isPopoverOpen: false,
- msg: false,
- newGroupName: '',
- });
- }
-
- clearGroupName() {
- this.setState({
- newGroupName: '',
- });
- }
-
- onChangeNewGroupName = (e) => {
- this.setState({
- newGroupName: e.target.value,
- });
- };
-
- /**
- * Looking for the input element to bind the keypress event, once the input is found the interval is clear
- */
- bindEnterToInput() {
- try {
- const interval = setInterval(async () => {
- const input = document.getElementsByClassName('groupNameInput');
- if (input.length) {
- const i = input[0];
- if (!i.onkeypress) {
- i.onkeypress = async (e) => {
- if (e.which === 13) {
- await this.createGroup();
- }
- };
- }
- clearInterval(interval);
- }
- }, 150);
- } catch (error) {
- const options = {
- context: `${WzGroupsActionButtonsAgents.name}.bindEnterToInput`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: error.message || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- async createGroup() {
- try {
- this.props.updateLoadingStatus(true);
- await this.groupsHandler.saveGroup(this.state.newGroupName);
- this.showToast('success', 'Success', 'The group has been created successfully', 2000);
- this.clearGroupName();
-
- this.props.updateIsProcessing(true);
- this.props.updateLoadingStatus(false);
- this.closePopover();
- } catch (error) {
- this.props.updateLoadingStatus(false);
- throw new Error(error);
- }
- }
-
- /**
- * 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
- await this.exportCsv(`/groups/${this.props.state.itemDetail.name}/agents`, filters, 'Groups');
- this.showToast(
- 'success',
- 'Success',
- 'CSV. Your download should begin automatically...',
- 2000
- );
- } catch (error) {
- const options = {
- context: `${WzGroupsActionButtonsAgents.name}.generateCsv`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: `Error when exporting the CSV file: ${error.message || error}`,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- this.setState({ generatingCsv: false });
- }
-
- showToast = (color, title, text, time) => {
- getToasts().add({
- color: color,
- title: title,
- text: text,
- toastLifeTimeMs: time,
- });
- };
-
render() {
// Add new group button
const manageAgentsButton = (
@@ -237,30 +63,11 @@ class WzGroupsActionButtonsAgents extends Component {
type="group"
/>
);
- // Export button
- const exportCSVButton = (
- await this.generateCsv()}
- isLoading={this.state.generatingCsv}
- >
- Export formatted
-
- );
-
- // Refresh
- const refreshButton = (
- await this.refresh()}>
- Refresh
-
- );
return (
{manageAgentsButton}
{exportPDFButton}
- {exportCSVButton}
- {refreshButton}
);
}
@@ -274,10 +81,7 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return {
- updateLoadingStatus: (status) => dispatch(updateLoadingStatus(status)),
- updateIsProcessing: (isProcessing) => dispatch(updateIsProcessing(isProcessing)),
updateShowAddAgents: (showAddAgents) => dispatch(updateShowAddAgents(showAddAgents)),
- updateReload: () => dispatch(updateReload()),
};
};
diff --git a/plugins/main/public/controllers/management/components/management/groups/actions-buttons-files.js b/plugins/main/public/controllers/management/components/management/groups/actions-buttons-files.js
index f32424dc95..2236648697 100644
--- a/plugins/main/public/controllers/management/components/management/groups/actions-buttons-files.js
+++ b/plugins/main/public/controllers/management/components/management/groups/actions-buttons-files.js
@@ -16,88 +16,24 @@ import { EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
import { connect } from 'react-redux';
import {
- updateLoadingStatus,
- updateIsProcessing,
updateFileContent,
} from '../../../../../redux/actions/groupsActions';
-import exportCsv from '../../../../../react-services/wz-csv';
import GroupsHandler from './utils/groups-handler';
-import { getToasts } from '../../../../../kibana-services';
import { ExportConfiguration } from '../../../../agent/components/export-configuration';
import { WzButtonPermissions } from '../../../../../components/common/permissions/button';
import { ReportingService } from '../../../../../react-services/reporting';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
class WzGroupsActionButtonsFiles extends Component {
- _isMounted = false;
-
constructor(props) {
super(props);
this.reportingService = new ReportingService();
- this.state = {
- generatingCsv: false,
- isPopoverOpen: false,
- newGroupName: '',
- };
- this.exportCsv = exportCsv;
-
this.groupsHandler = GroupsHandler;
this.refreshTimeoutId = null;
}
- componentDidMount() {
- this._isMounted = true;
- if (this._isMounted) this.bindEnterToInput();
- }
-
- componentDidUpdate() {
- this.bindEnterToInput();
- }
-
- componentWillUnmount() {
- this._isMounted = false;
- }
-
- /**
- * Refresh the items
- */
- async refresh() {
- try {
- this.props.updateIsProcessing(true);
- this.onRefreshLoading();
- } catch (error) {
- const options = {
- context: `${WzGroupsActionButtonsFiles.name}.refresh`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- onRefreshLoading() {
- clearInterval(this.refreshTimeoutId);
-
- this.props.updateLoadingStatus(true);
- this.refreshTimeoutId = setInterval(() => {
- if (!this.props.state.isProcessing) {
- this.props.updateLoadingStatus(false);
- clearInterval(this.refreshTimeoutId);
- }
- }, 100);
- }
-
autoFormat = (xml) => {
var reg = /(>)\s*(<)(\/*)/g;
var wsexp = / *(.*) +\n/g;
@@ -173,117 +109,6 @@ class WzGroupsActionButtonsFiles extends Component {
this.props.updateFileContent(file);
}
- closePopover() {
- this.setState({
- isPopoverOpen: false,
- msg: false,
- newGroupName: '',
- });
- }
-
- clearGroupName() {
- this.setState({
- newGroupName: '',
- });
- }
-
- onChangeNewGroupName = (e) => {
- this.setState({
- newGroupName: e.target.value,
- });
- };
-
- /**
- * Looking for the input element to bind the keypress event, once the input is found the interval is clear
- */
- bindEnterToInput() {
- try {
- const interval = setInterval(async () => {
- const input = document.getElementsByClassName('groupNameInput');
- if (input.length) {
- const i = input[0];
- if (!i.onkeypress) {
- i.onkeypress = async (e) => {
- if (e.which === 13) {
- await this.createGroup();
- }
- };
- }
- clearInterval(interval);
- }
- }, 150);
- } catch (error) {
- const options = {
- context: `${WzGroupsActionButtonsFiles.name}.bindEnterToInput`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: error.name || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- async createGroup() {
- try {
- this.props.updateLoadingStatus(true);
- await this.groupsHandler.saveGroup(this.state.newGroupName);
- this.showToast('success', 'Success', 'The group has been created successfully', 2000);
- this.clearGroupName();
-
- this.props.updateIsProcessing(true);
- this.props.updateLoadingStatus(false);
- this.closePopover();
- } catch (error) {
- this.props.updateLoadingStatus(false);
- throw new Error(error);
- }
- }
-
- /**
- * 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
- await this.exportCsv(`/groups/${this.props.state.itemDetail.name}/files`, filters, 'Groups');
- this.showToast(
- 'success',
- 'Success',
- 'CSV. Your download should begin automatically...',
- 2000
- );
- } catch (error) {
- const options = {
- context: `${WzGroupsActionButtonsFiles.name}.generateCsv`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: `Error when exporting the CSV file: ${error.message || error}`,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- this.setState({ generatingCsv: false });
- }
-
- showToast = (color, title, text, time) => {
- getToasts().add({
- color: color,
- title: title,
- text: text,
- toastLifeTimeMs: time,
- });
- };
-
render() {
// Add new group button
const groupConfigurationButton = (
@@ -313,30 +138,11 @@ class WzGroupsActionButtonsFiles extends Component {
type="group"
/>
);
- // Export button
- const exportCSVButton = (
- await this.generateCsv()}
- isLoading={this.state.generatingCsv}
- >
- Export formatted
-
- );
-
- // Refresh
- const refreshButton = (
- await this.refresh()}>
- Refresh
-
- );
return (
{groupConfigurationButton}
{exportPDFButton}
- {exportCSVButton}
- {refreshButton}
);
}
@@ -350,8 +156,6 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = (dispatch) => {
return {
- updateLoadingStatus: (status) => dispatch(updateLoadingStatus(status)),
- updateIsProcessing: (isProcessing) => dispatch(updateIsProcessing(isProcessing)),
updateFileContent: (content) => dispatch(updateFileContent(content)),
};
};
diff --git a/plugins/main/public/controllers/management/components/management/groups/actions-buttons-main.js b/plugins/main/public/controllers/management/components/management/groups/actions-buttons-main.js
index d286ab652c..9e6b7cc061 100644
--- a/plugins/main/public/controllers/management/components/management/groups/actions-buttons-main.js
+++ b/plugins/main/public/controllers/management/components/management/groups/actions-buttons-main.js
@@ -9,28 +9,18 @@
*
* Find more information about this on the LICENSE file.
*/
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
// Eui components
import {
EuiFlexItem,
- EuiButtonEmpty,
EuiPopover,
EuiFormRow,
EuiFieldText,
- EuiSpacer,
EuiFlexGroup,
- EuiButton,
} from '@elastic/eui';
-import { connect } from 'react-redux';
import { WzButtonPermissions } from '../../../../../components/common/permissions/button';
-import {
- updateLoadingStatus,
- updateIsProcessing,
-} from '../../../../../redux/actions/groupsActions';
-
-import exportCsv from '../../../../../react-services/wz-csv';
import GroupsHandler from './utils/groups-handler';
import { getToasts } from '../../../../../kibana-services';
import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
@@ -45,14 +35,10 @@ class WzGroupsActionButtons extends Component {
super(props);
this.state = {
- generatingCsv: false,
isPopoverOpen: false,
newGroupName: '',
};
- this.exportCsv = exportCsv;
- this.groupsHandler = GroupsHandler;
- this.refreshTimeoutId = null;
}
componentDidMount() {
@@ -68,41 +54,6 @@ class WzGroupsActionButtons extends Component {
this._isMounted = false;
}
- /**
- * Refresh the items
- */
- async refresh() {
- try {
- this.props.updateIsProcessing(true);
- this.onRefreshLoading();
- } catch (error) {
- const options = {
- context: `${WzGroupsActionButtons.name}.refresh`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: error.message || error,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- onRefreshLoading() {
- clearInterval(this.refreshTimeoutId);
-
- this.props.updateLoadingStatus(true);
- this.refreshTimeoutId = setInterval(() => {
- if (!this.props.state.isProcessing) {
- this.props.updateLoadingStatus(false);
- clearInterval(this.refreshTimeoutId);
- }
- }, 100);
- }
-
togglePopover() {
if (this.state.isPopoverOpen) {
this.closePopover();
@@ -169,13 +120,11 @@ class WzGroupsActionButtons extends Component {
async createGroup() {
try {
if (this.isOkNameGroup(this.state.newGroupName)) {
- this.props.updateLoadingStatus(true);
- await this.groupsHandler.saveGroup(this.state.newGroupName);
+ await GroupsHandler.saveGroup(this.state.newGroupName);
this.showToast('success', 'Success', 'The group has been created successfully', 2000);
this.clearGroupName();
- this.props.updateIsProcessing(true);
- this.props.updateLoadingStatus(false);
+ this.props.reloadTable();
this.closePopover();
}
} catch (error) {
@@ -192,42 +141,10 @@ class WzGroupsActionButtons extends Component {
},
};
getErrorOrchestrator().handleError(options);
- this.props.updateLoadingStatus(false);
throw new Error(error);
}
}
- /**
- * 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
- await this.exportCsv('/groups', filters, 'Groups');
- this.showToast(
- 'success',
- 'Success',
- 'CSV. Your download should begin automatically...',
- 2000
- );
- } catch (error) {
- const options = {
- context: `${WzGroupsActionButtons.name}.generateCsv`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: `Error when exporting the CSV file: ${error.message || error}`,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- this.setState({ generatingCsv: false });
- }
-
showToast = (color, title, text, time) => {
getToasts().add({
color: color,
@@ -255,78 +172,41 @@ class WzGroupsActionButtons extends Component {
);
- // Export button
- const exportButton = (
- await this.generateCsv()}
- isLoading={this.state.generatingCsv}
- >
- Export formatted
-
- );
-
- // Refresh
- const refreshButton = (
- await this.refresh()}>
- Refresh
-
- );
-
return (
-
-
- this.closePopover()}
- >
-
-
-
-
-
-
-
- {
- await this.createGroup();
- }}
- >
- Save new group
-
-
-
-
-
- {exportButton}
- {refreshButton}
-
+ this.closePopover()}
+ >
+
+
+
+
+
+
+
+ {
+ await this.createGroup();
+ }}
+ >
+ Save new group
+
+
+
+
);
}
}
-const mapStateToProps = (state) => {
- return {
- state: state.groupsReducers,
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- updateLoadingStatus: (status) => dispatch(updateLoadingStatus(status)),
- updateIsProcessing: (isProcessing) => dispatch(updateIsProcessing(isProcessing)),
- };
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(WzGroupsActionButtons);
+export default WzGroupsActionButtons;
diff --git a/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js b/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js
index e7c4a11aba..f1b6225e9e 100644
--- a/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js
+++ b/plugins/main/public/controllers/management/components/management/groups/group-agents-table.js
@@ -9,7 +9,8 @@
*
* Find more information about this on the LICENSE file.
*/
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
+import { EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { connect } from 'react-redux';
import GroupsHandler from './utils/groups-handler';
@@ -25,171 +26,80 @@ import {
updateSortFieldAgents,
updateReload,
} from '../../../../../redux/actions/groupsActions';
-import { EuiCallOut } from '@elastic/eui';
-import { getAgentFilterValues } from './get-agents-filters-values';
import { TableWzAPI } from '../../../../../components/common/tables';
import { WzButtonPermissions } from '../../../../../components/common/permissions/button';
import { WzButtonPermissionsModalConfirm } from '../../../../../components/common/buttons';
import {
+ SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
UI_LOGGER_LEVELS,
UI_ORDER_AGENT_STATUS,
} from '../../../../../../common/constants';
+import { get as getLodash } from 'lodash';
import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
import { getErrorOrchestrator } from '../../../../../react-services/common-services';
+import { AgentStatus } from '../../../../../components/agents/agent_status';
+import { WzRequest } from '../../../../../react-services';
class WzGroupAgentsTable extends Component {
_isMounted = false;
constructor(props) {
super(props);
- this.suggestions = [
- {
- type: 'q',
- label: 'status',
- description: 'Filter by agent connection status',
- operators: ['=', '!='],
- values: UI_ORDER_AGENT_STATUS,
- },
- {
- type: 'q',
- label: 'os.platform',
- description: 'Filter by operating system platform',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('os.platform', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'ip',
- description: 'Filter by agent IP address',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('ip', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'name',
- description: 'Filter by agent name',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('name', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'id',
- description: 'Filter by agent id',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('id', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'node_name',
- description: 'Filter by node name',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('node_name', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'manager',
- description: 'Filter by manager',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('manager', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'version',
- description: 'Filter by agent version',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('version', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'configSum',
- description: 'Filter by agent config sum',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('configSum', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- {
- type: 'q',
- label: 'mergedSum',
- description: 'Filter by agent merged sum',
- operators: ['=', '!='],
- values: async value =>
- getAgentFilterValues('mergedSum', value, {
- q: `group=${this.props.state.itemDetail.name}`,
- }),
- },
- //{ type: 'q', label: 'dateAdd', description: 'Filter by add date', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('dateAdd', value, {q: `group=${this.props.state.itemDetail.name}`})},
- //{ type: 'q', label: 'lastKeepAlive', description: 'Filter by last keep alive', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('lastKeepAlive', value, {q: `group=${this.props.state.itemDetail.name}`})},
- ];
- this.groupsHandler = GroupsHandler;
this.columns = [
{
field: 'id',
name: 'Id',
align: 'left',
+ searchable: true,
sortable: true,
},
{
field: 'name',
name: 'Name',
align: 'left',
+ searchable: true,
sortable: true,
},
{
field: 'ip',
name: 'IP address',
- sortable: true,
- show: true,
- },
- {
- field: 'status',
- name: 'Status',
align: 'left',
+ searchable: true,
sortable: true,
},
{
- field: 'os.name',
- name: 'Operating system name',
+ field: 'os.name,os.version',
+ composeField: ['os.name', 'os.version'],
+ name: 'Operating system',
align: 'left',
+ searchable: true,
sortable: true,
+ render: (field, agentData) => this.addIconPlatformRender(agentData),
},
{
- field: 'os.version',
- name: 'Operating system version',
+ field: 'version',
+ name: 'Version',
align: 'left',
+ searchable: true,
sortable: true,
},
{
- field: 'version',
- name: 'Version',
+ field: 'status',
+ name: 'Status',
align: 'left',
+ searchable: true,
sortable: true,
+ render: status => (
+
+ ),
},
{
name: 'Actions',
align: 'left',
+ searchable: false,
render: item => {
return (
@@ -251,20 +161,105 @@ class WzGroupAgentsTable extends Component {
},
},
];
+
+ this.searchBar = {
+ wql: {
+ suggestionFields: [
+ { label: 'id', description: `filter by ID` },
+ { label: 'ip', description: `filter by IP address` },
+ { label: 'name', description: `filter by Name` },
+ { label: 'os.name', description: `filter by Operating system name` },
+ {
+ label: 'os.version',
+ description: `filter by Operating system version`,
+ },
+ { label: 'status', description: `filter by Status` },
+ { label: 'version', description: `filter by Version` },
+ ],
+ },
+ };
}
componentWillUnmount() {
this._isMounted = false;
}
+
+ addIconPlatformRender(agent) {
+ let icon = '';
+ const os = agent?.os || {};
+
+ if ((os?.uname || '').includes('Linux')) {
+ icon = 'linux';
+ } else if (os?.platform === 'windows') {
+ icon = 'windows';
+ } else if (os?.platform === 'darwin') {
+ icon = 'apple';
+ }
+ const os_name = `${agent?.os?.name || ''} ${agent?.os?.version || ''}`;
+
+ return (
+
+
+
+ {' '}
+ {os_name.trim() || '-'}
+
+ );
+ }
+
render() {
const { error } = this.props.state;
+ const groupName = this.props.state?.itemDetail?.name;
+ const searchBarSuggestionsFields = this.searchBar.wql.suggestionFields;
if (!error) {
return (
searchBarSuggestionsFields,
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/groups/${groupName}/agents`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ mapResponseItem={item => ({
+ ...item,
+ ...(item.ip ? { ip: item.ip } : { ip: '-' }),
+ ...(typeof item.version === 'string'
+ ? { version: item.version.match(/(v\d.+)/)?.[1] }
+ : { version: '-' }),
+ })}
+ showReload
+ downloadCsv={`agents-group-${groupName}`}
reload={this.props.state.reload}
searchTable={true}
tableProps={{ tableLayout: 'auto' }}
@@ -289,9 +284,7 @@ class WzGroupAgentsTable extends Component {
this.props.updateLoadingStatus(true);
try {
await Promise.all(
- items.map(item =>
- this.groupsHandler.deleteAgent(item.id, itemDetail.name),
- ),
+ items.map(item => GroupsHandler.deleteAgent(item.id, itemDetail.name)),
);
this.props.updateIsProcessing(true);
this.props.updateLoadingStatus(false);
diff --git a/plugins/main/public/controllers/management/components/management/groups/group-detail.js b/plugins/main/public/controllers/management/components/management/groups/group-detail.js
index 99ca3e3fc9..bbfc161651 100644
--- a/plugins/main/public/controllers/management/components/management/groups/group-detail.js
+++ b/plugins/main/public/controllers/management/components/management/groups/group-detail.js
@@ -83,40 +83,13 @@ class WzGroupDetail extends Component {
renderAgents() {
return (
-
-
-
-
- From here you can list and manage your agents
-
-
-
-
-
-
-
-
-
+
);
}
renderFiles() {
return (
-
-
-
-
- From here you can list and see your group files, also, you can
- edit the group configuration
-
-
-
-
-
-
-
-
-
+
);
}
@@ -142,7 +115,7 @@ class WzGroupDetail extends Component {
-
+
{itemDetail.name}
diff --git a/plugins/main/public/controllers/management/components/management/groups/group-files-table.js b/plugins/main/public/controllers/management/components/management/groups/group-files-table.js
index b919bac86c..b5a0ed3f41 100644
--- a/plugins/main/public/controllers/management/components/management/groups/group-files-table.js
+++ b/plugins/main/public/controllers/management/components/management/groups/group-files-table.js
@@ -9,12 +9,8 @@
*
* Find more information about this on the LICENSE file.
*/
-import React, { Component, Fragment } from 'react';
-import { EuiBasicTable, EuiCallOut, EuiSpacer } from '@elastic/eui';
-
+import React, { Component } from 'react';
import { connect } from 'react-redux';
-import GroupsHandler from './utils/groups-handler';
-import { getToasts } from '../../../../../kibana-services';
import {
updateLoadingStatus,
@@ -22,185 +18,86 @@ import {
updatePageIndexFile,
updateSortDirectionFile,
updateSortFieldFile,
- updateFileContent
+ updateFileContent,
} from '../../../../../redux/actions/groupsActions';
import GroupsFilesColumns from './utils/columns-files';
-import { WzSearchBar, filtersToObject } from '../../../../../components/wz-search-bar';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-
+import { TableWzAPI } from '../../../../../components/common/tables';
+import { WzRequest } from '../../../../../react-services';
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../../common/constants';
class WzGroupFilesTable extends Component {
_isMounted = false;
- suggestions = [
- //{ type: 'q', label: 'filename', description: 'Filter by file name', operators: ['=', '!=',], values: async (value) => getGroupsFilesValues('filename', value, {},this.props.state.itemDetail.name )},
- //{ type: 'params', label: 'hash', description: 'Filter by hash', operators: ['=', '!=',], values: async (value) => getGroupsFilesValues('hash', value, {},this.props.state.itemDetail.name )},
- ];
constructor(props) {
super(props);
this.state = {
- items: [],
- pageSize: 10,
- totalItems: 0,
- filters: []
+ filters: {},
};
- this.groupsHandler = GroupsHandler;
- }
-
- async componentDidMount() {
- await this.getItems();
- this._isMounted = true;
- }
-
- async componentDidUpdate(prevProps, prevState) {
- if (this.props.state.isProcessing && this._isMounted) {
- await this.getItems();
- }
- const { filters } = this.state;
- if (JSON.stringify(filters) !== JSON.stringify(prevState.filters)) {
- await this.getItems();
- }
- }
-
- componentWillUnmount() {
- this._isMounted = false;
- }
-
- /**
- * Loads the initial information
- */
- async getItems() {
- try {
- const rawItems = await this.groupsHandler.filesGroup(
- this.props.state.itemDetail.name,
- { params: this.buildFilter() }
- );
- const { affected_items, total_affected_items } = ((rawItems || {}).data || {}).data;
-
- this.setState({
- items: affected_items,
- totalItems: total_affected_items,
- isProcessing: false
- });
- this.props.state.isProcessing && this.props.updateIsProcessing(false);
- } catch (error) {
- this.props.state.isProcessing && this.props.updateIsProcessing(false);
- const options = {
- context: `${WzGroupFilesTable.name}.getItems`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.CRITICAL,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: `Error loading the groups: ${error.message || error}`,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
- }
-
- buildFilter() {
- const { pageIndexFile } = this.props.state;
- const { pageSize, filters } = this.state;
- const filter = {
- ...filtersToObject(filters),
- offset: pageIndexFile * pageSize,
- limit: pageSize,
- sort: this.buildSortFilter()
+ this.searchBar = {
+ wql: {
+ suggestionsFields: [
+ { label: 'filename', description: 'filter by filename' },
+ { label: 'hash', description: 'filter by hash' },
+ ],
+ },
};
-
- return filter;
- }
-
- buildSortFilter() {
- const { sortFieldFile, sortDirectionFile } = this.props.state;
-
- const field = sortFieldFile;
- const direction = sortDirectionFile === 'asc' ? '+' : '-';
-
- return direction + field;
}
- onTableChange = ({ page = {}, sort = {} }) => {
- const { index: pageIndexFile, size: pageSize } = page;
- const { field: sortFieldFile, direction: sortDirectionFile } = sort;
- this.setState({ pageSize });
- this.props.updatePageIndexFile(pageIndexFile);
- this.props.updateSortDirectionFile(sortDirectionFile);
- this.props.updateSortFieldFile(sortFieldFile);
- this.props.updateIsProcessing(true);
- };
-
render() {
this.groupsAgentsColumns = new GroupsFilesColumns(this.props);
- const {
- isLoading,
- pageIndexFile,
- error,
- sortFieldFile,
- sortDirectionFile
- } = this.props.state;
- const { items, pageSize, totalItems, filters } = this.state;
const columns = this.groupsAgentsColumns.columns;
- const message = isLoading ? null : 'No results...';
- const pagination = {
- pageIndex: pageIndexFile,
- pageSize: pageSize,
- totalItemCount: totalItems,
- pageSizeOptions: [10, 25, 50, 100]
- };
- const sorting = {
- sort: {
- field: sortFieldFile,
- direction: sortDirectionFile
- }
- };
-
- if (!error) {
- return (
-
- this.setState({filters})}
- placeholder='Search file'
- />
-
-
-
- );
- } else {
- return ;
- }
+ const groupName = this.props.state?.itemDetail?.name;
+ const searchBarWQL = this.searchBar.wql;
+
+ return (
+ searchBarWQL.suggestionsFields,
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ `/groups/${groupName}/files`,
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ },
+ );
+ return response?.data?.data.affected_items.map(item => ({
+ label: item[field],
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ showReload
+ downloadCsv={`files-group-${groupName}`}
+ searchTable={true}
+ />
+ );
}
-
- showToast = (color, title, text, time) => {
- getToasts().add({
- color: color,
- title: title,
- text: text,
- toastLifeTimeMs: time
- });
- };
}
const mapStateToProps = state => {
return {
- state: state.groupsReducers
+ state: state.groupsReducers,
};
};
@@ -215,11 +112,8 @@ const mapDispatchToProps = dispatch => {
dispatch(updateSortDirectionFile(sortDirectionFile)),
updateSortFieldFile: sortFieldFile =>
dispatch(updateSortFieldFile(sortFieldFile)),
- updateFileContent: content => dispatch(updateFileContent(content))
+ updateFileContent: content => dispatch(updateFileContent(content)),
};
};
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(WzGroupFilesTable);
+export default connect(mapStateToProps, mapDispatchToProps)(WzGroupFilesTable);
diff --git a/plugins/main/public/controllers/management/components/management/groups/groups-overview.js b/plugins/main/public/controllers/management/components/management/groups/groups-overview.js
index 54218b174f..e3d7046d38 100644
--- a/plugins/main/public/controllers/management/components/management/groups/groups-overview.js
+++ b/plugins/main/public/controllers/management/components/management/groups/groups-overview.js
@@ -12,58 +12,326 @@
*/
import React, { Component } from 'react';
import {
- EuiFlexItem,
- EuiFlexGroup,
EuiPanel,
- EuiTitle,
- EuiText,
- EuiPage
+ EuiPage,
+ EuiOverlayMask,
+ EuiConfirmModal,
} from '@elastic/eui';
// Wazuh components
-import WzGroupsTable from './groups-table';
import WzGroupsActionButtons from './actions-buttons-main';
import { connect } from 'react-redux';
-import { withUserAuthorizationPrompt } from '../../../../../components/common/hocs'
+import {
+ withUserAuthorizationPrompt,
+ withUserPermissions,
+} from '../../../../../components/common/hocs';
import { compose } from 'redux';
+import { TableWzAPI } from '../../../../../components/common/tables';
+import { WzButtonPermissions } from '../../../../../components/common/permissions/button';
+import {
+ updateFileContent,
+ updateGroupDetail,
+ updateListItemsForRemove,
+ updateShowModal,
+} from '../../../../../redux/actions/groupsActions';
+import { WzRequest, WzUserPermissions } from '../../../../../react-services';
+import { getToasts } from '../../../../../kibana-services';
+import GroupsHandler from './utils/groups-handler';
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../../common/constants';
export class WzGroupsOverview extends Component {
_isMounted = false;
constructor(props) {
super(props);
+ this.state = {
+ reload: Date.now(),
+ };
+ this.tableColumns = [
+ {
+ field: 'name',
+ name: 'Name',
+ align: 'left',
+ searchable: true,
+ sortable: true,
+ },
+ {
+ field: 'count',
+ name: 'Agents',
+ align: 'left',
+ searchable: true,
+ sortable: true,
+ },
+ {
+ field: 'configSum',
+ name: 'Configuration checksum',
+ align: 'left',
+ searchable: true,
+ },
+ {
+ name: 'Actions',
+ align: 'left',
+ searchable: false,
+ render: item => {
+ return (
+
+ {
+ this.props.updateGroupDetail(item);
+ }}
+ color='primary'
+ />
+ {
+ ev.stopPropagation();
+ this.showGroupConfiguration(item.name);
+ }}
+ />
+ {
+ ev.stopPropagation();
+ this.props.updateListItemsForRemove([item]);
+ this.props.updateShowModal(true);
+ }}
+ color='danger'
+ isDisabled={item.name === 'default'}
+ />
+
+ );
+ },
+ },
+ ];
+ this.reloadTable = this.reloadTable.bind(this);
+ }
+
+ reloadTable() {
+ this.setState({ reload: Date.now() });
+ }
+
+ async removeItems(items) {
+ try {
+ const promises = items.map(
+ async (item, i) => await GroupsHandler.deleteGroup(item.name),
+ );
+ await Promise.all(promises);
+ getToasts().add({
+ color: 'success',
+ title: 'Success',
+ text: 'Deleted successfully',
+ toastLifeTimeMs: 3000,
+ });
+ } catch (error) {
+ getToasts().add({
+ color: 'danger',
+ title: 'Error',
+ text: error,
+ toastLifeTimeMs: 3000,
+ });
+ } finally {
+ this.reloadTable();
+ }
+ }
+
+ async showGroupConfiguration(groupId) {
+ const result = await GroupsHandler.getFileContent(
+ `/groups/${groupId}/files/agent.conf/xml`,
+ );
+
+ const file = {
+ name: 'agent.conf',
+ content: this.autoFormat(result),
+ isEditable: true,
+ groupName: groupId,
+ };
+ this.props.updateFileContent(file);
}
+ autoFormat = xml => {
+ var reg = /(>)\s*(<)(\/*)/g;
+ var wsexp = / *(.*) +\n/g;
+ var contexp = /(<.+>)(.+\n)/g;
+ xml = xml
+ .replace(reg, '$1\n$2$3')
+ .replace(wsexp, '$1\n')
+ .replace(contexp, '$1\n$2');
+ var formatted = '';
+ var lines = xml.split('\n');
+ var indent = 0;
+ var lastType = 'other';
+ var transitions = {
+ 'single->single': 0,
+ 'single->closing': -1,
+ 'single->opening': 0,
+ 'single->other': 0,
+ 'closing->single': 0,
+ 'closing->closing': -1,
+ 'closing->opening': 0,
+ 'closing->other': 0,
+ 'opening->single': 1,
+ 'opening->closing': 0,
+ 'opening->opening': 1,
+ 'opening->other': 1,
+ 'other->single': 0,
+ 'other->closing': -1,
+ 'other->opening': 0,
+ 'other->other': 0,
+ };
+
+ for (var i = 0; i < lines.length; i++) {
+ var ln = lines[i];
+ if (ln.match(/\s*<\?xml/)) {
+ formatted += ln + '\n';
+ continue;
+ }
+ var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex.
+ var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex.
+ var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not )
+ var type = single
+ ? 'single'
+ : closing
+ ? 'closing'
+ : opening
+ ? 'opening'
+ : 'other';
+ var fromTo = lastType + '->' + type;
+ lastType = type;
+ var padding = '';
+
+ indent += transitions[fromTo];
+ for (var j = 0; j < indent; j++) {
+ padding += '\t';
+ }
+ if (fromTo == 'opening->closing')
+ formatted = formatted.substr(0, formatted.length - 1) + ln + '\n';
+ // substr removes line break (\n) from prev loop
+ else formatted += padding + ln + '\n';
+ }
+ return formatted.trim();
+ };
+
render() {
+ const actionButtons = [
+ ,
+ ];
+
+ const getRowProps = item => {
+ const { id } = item;
+ return {
+ 'data-test-subj': `row-${id}`,
+ className: 'customRowClass',
+ onClick: !WzUserPermissions.checkMissingUserPermissions(
+ [{ action: 'group:read', resource: `group:id:${item.name}` }],
+ this.props.userPermissions,
+ )
+ ? () => this.props.updateGroupDetail(item)
+ : undefined,
+ };
+ };
+
return (
-
-
-
-
-
- Groups
-
-
-
-
-
-
-
-
-
- From here you can list and check your groups, its agents and
- files.
-
-
-
-
-
-
-
-
+ [
+ { label: 'name', description: 'filter by name' },
+ { label: 'count', description: 'filter by count' },
+ {
+ label: 'configSum',
+ description: 'filter by configuration checksum',
+ },
+ ],
+ value: async (currentValue, { field }) => {
+ try {
+ const response = await WzRequest.apiReq('GET', '/groups', {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? { q: `${field}~${currentValue}` }
+ : {}),
+ },
+ });
+ return response?.data?.data.affected_items.map(item => ({
+ label: item[field],
+ }));
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
+ rowProps={getRowProps}
+ endpoint={'/groups'}
+ downloadCsv={true}
+ showReload={true}
+ tablePageSizeOptions={[10, 25, 50, 100]}
+ />
+ {this.props.state.showModal ? (
+
+ this.props.updateShowModal(false)}
+ onConfirm={() => {
+ this.removeItems(this.props.state.itemList);
+ this.props.updateShowModal(false);
+ }}
+ cancelButtonText='Cancel'
+ confirmButtonText='Delete'
+ defaultFocusedButton='cancel'
+ buttonColor='danger'
+ >
+
+ ) : null}
);
}
@@ -71,14 +339,22 @@ export class WzGroupsOverview extends Component {
const mapStateToProps = state => {
return {
- state: state.groupsReducers
+ state: state.groupsReducers,
};
};
+const mapDispatchToProps = dispatch => ({
+ updateShowModal: showModal => dispatch(updateShowModal(showModal)),
+ updateListItemsForRemove: itemList =>
+ dispatch(updateListItemsForRemove(itemList)),
+ updateGroupDetail: itemDetail => dispatch(updateGroupDetail(itemDetail)),
+ updateFileContent: content => dispatch(updateFileContent(content)),
+});
export default compose(
- withUserAuthorizationPrompt([{action: 'group:read', resource: 'group:id:*'}]),
- connect(
- mapStateToProps
- ),
+ withUserAuthorizationPrompt([
+ { action: 'group:read', resource: 'group:id:*' },
+ ]),
+ connect(mapStateToProps, mapDispatchToProps),
+ withUserPermissions,
)(WzGroupsOverview);
diff --git a/plugins/main/public/controllers/management/components/management/groups/groups-table.js b/plugins/main/public/controllers/management/components/management/groups/groups-table.js
deleted file mode 100644
index 421fcbe1ad..0000000000
--- a/plugins/main/public/controllers/management/components/management/groups/groups-table.js
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Wazuh app - React component for groups main table.
- * Copyright (C) 2015-2022 Wazuh, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Find more information about this on the LICENSE file.
- */
-import React, { Component, Fragment } from 'react';
-import {
- EuiBasicTable,
- EuiCallOut,
- EuiOverlayMask,
- EuiConfirmModal,
- EuiSpacer,
-} from '@elastic/eui';
-
-import { connect } from 'react-redux';
-import { compose } from 'redux';
-import GroupsHandler from './utils/groups-handler';
-import { getToasts } from '../../../../../kibana-services';
-import { WzSearchBar, filtersToObject } from '../../../../../components/wz-search-bar';
-import { withUserPermissions } from '../../../../../components/common/hocs/withUserPermissions';
-import { WzUserPermissions } from '../../../../../react-services/wz-user-permissions';
-import { UI_LOGGER_LEVELS } from '../../../../../../common/constants';
-import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types';
-import { getErrorOrchestrator } from '../../../../../react-services/common-services';
-
-
-import {
- updateLoadingStatus,
- updateFileContent,
- updateIsProcessing,
- updatePageIndex,
- updateShowModal,
- updateListItemsForRemove,
- updateSortDirection,
- updateSortField,
- updateGroupDetail,
-} from '../../../../../redux/actions/groupsActions';
-
-import GroupsColums from './utils/columns-main';
-
-class WzGroupsTable extends Component {
- _isMounted = false;
-
- suggestions = []; //TODO: Fix suggestions without q search for API 4.0
-
- constructor(props) {
- super(props);
- this.state = {
- items: [],
- pageSize: 10,
- totalItems: 0,
- filters: [],
- };
-
- this.groupsHandler = GroupsHandler;
- }
-
- async componentDidMount() {
- this._isMounted = true;
- await this.getItems();
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- const { items, filters } = this.state;
- const { isProcessing, showModal, isLoading } = this.props.state;
- if (showModal !== nextProps.state.showModal) return true;
- if (isProcessing !== nextProps.state.isProcessing) return true;
- if (JSON.stringify(items) !== JSON.stringify(nextState.items)) return true;
- if (JSON.stringify(filters) !== JSON.stringify(nextState.filters)) return true;
- if (isLoading !== nextProps.state.isLoading) return true;
- return false;
- }
-
- async componentDidUpdate(prevProps, prevState) {
- const { filters } = this.state;
- if ((JSON.stringify(filters) !== JSON.stringify(prevState.filters)) ||
- /**
- Is verifying that isProcessing is true and that it has changed its value,
- since in the shouldComponentUpdate it is making it re-execute several times
- each time a state changes, regardless of whether it is a change in isProcessing.
- */
- (
- prevProps.state.isProcessing !== this.props.state.isProcessing &&
- this.props.state.isProcessing &&
- this._isMounted
- )
- ) {
- await this.getItems();
- }
- }
-
- componentWillUnmount() {
- this._isMounted = false;
- }
-
- /**
- * Loads the initial information
- */
- async getItems() {
- try {
- this.props.updateLoadingStatus(true);
- const rawItems = await this.groupsHandler.listGroups({ params: this.buildFilter() });
- const {
- affected_items: affectedItem,
- total_affected_items: totalAffectedItem
- } = rawItems?.data?.data;
- this.setState({
- items: affectedItem,
- totalItems: totalAffectedItem,
- });
- this.props.updateLoadingStatus(false);
- this.props.state.isProcessing && this.props.updateIsProcessing(false);
-
- } catch (error) {
- this.props.updateLoadingStatus(false);
- this.props.state.isProcessing && this.props.updateIsProcessing(false);
- const options = {
- context: `${WzGroupsTable.name}.getItems`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.CRITICAL,
- store: true,
- error: {
- error: error,
- message: error.message || error,
- title: `Error getting groups`,
- },
- };
- getErrorOrchestrator().handleError(options);
- }
-
- }
-
- buildFilter() {
- const { pageIndex } = this.props.state;
- const { pageSize, filters } = this.state;
- const filter = {
- ...filtersToObject(filters),
- offset: pageIndex * pageSize,
- limit: pageSize,
- sort: this.buildSortFilter(),
- };
-
- return filter;
- }
-
- buildSortFilter() {
- const { sortField, sortDirection } = this.props.state;
-
- const field = sortField;
- const direction = sortDirection === 'asc' ? '+' : '-';
-
- return direction + field;
- }
-
- onTableChange = ({ page = {}, sort = {} }) => {
- const { index: pageIndex, size: pageSize } = page;
- const { field: sortField, direction: sortDirection } = sort;
- this._isMounted && this.setState({ pageSize });
- this.props.updatePageIndex(pageIndex);
- this.props.updateSortDirection(sortDirection);
- this.props.updateSortField(sortField);
- this.props.updateIsProcessing(true);
- };
-
- render() {
- const { filters } = this.state;
-
- this.groupsColumns = new GroupsColums(this.props);
- const { isLoading, pageIndex, error, sortField, sortDirection } = this.props.state;
- const { items, pageSize, totalItems } = this.state;
- const columns = this.groupsColumns.columns;
- const message = isLoading ? null : 'No results...';
- const pagination = {
- pageIndex: pageIndex,
- pageSize: pageSize,
- totalItemCount: totalItems,
- pageSizeOptions: [10, 25, 50, 100],
- };
- const sorting = {
- sort: {
- field: sortField,
- direction: sortDirection,
- },
- };
- const getRowProps = (item) => {
- const { id } = item;
- return {
- 'data-test-subj': `row-${id}`,
- className: 'customRowClass',
- onClick: !WzUserPermissions.checkMissingUserPermissions(
- [{ action: 'group:read', resource: `group:id:${item.name}` }],
- this.props.userPermissions
- )
- ? () => this.props.updateGroupDetail(item)
- : undefined,
- };
- };
-
- if (error) {
- return ;
- }
- const itemList = this.props.state.itemList;
- return (
-
- this._isMounted && this.setState({ filters })}
- placeholder="Search group"
- />
-
-
- {this.props.state.showModal ? (
-
- this.props.updateShowModal(false)}
- onConfirm={() => {
- this.removeItems(itemList);
- this.props.updateShowModal(false);
- }}
- cancelButtonText="Cancel"
- confirmButtonText="Delete"
- defaultFocusedButton="cancel"
- buttonColor="danger"
- >
-
- ) : null}
-
- );
- }
-
- showToast = (color, title, text, time) => {
- getToasts().add({
- color: color,
- title: title,
- text: text,
- toastLifeTimeMs: time,
- });
- };
-
- async removeItems(items) {
- this.props.updateLoadingStatus(true);
- const results = items.map(async (item, i) => {
- await this.groupsHandler.deleteGroup(item.name);
- });
-
- Promise.all(results).then(
- (completed) => {
- this.props.updateIsProcessing(true);
- this.props.updateLoadingStatus(false);
- this.showToast('success', 'Success', 'Deleted successfully', 3000);
- },
- (error) => {
- this.props.updateIsProcessing(true);
- this.props.updateLoadingStatus(false);
- this.showToast('danger', 'Error', error, 3000);
- }
- );
- }
-}
-
-const mapStateToProps = (state) => {
- return {
- state: state.groupsReducers,
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- updateLoadingStatus: (status) => dispatch(updateLoadingStatus(status)),
- updateFileContent: (content) => dispatch(updateFileContent(content)),
- updateIsProcessing: (isProcessing) => dispatch(updateIsProcessing(isProcessing)),
- updatePageIndex: (pageIndex) => dispatch(updatePageIndex(pageIndex)),
- updateShowModal: (showModal) => dispatch(updateShowModal(showModal)),
- updateListItemsForRemove: (itemList) => dispatch(updateListItemsForRemove(itemList)),
- updateSortDirection: (sortDirection) => dispatch(updateSortDirection(sortDirection)),
- updateSortField: (sortField) => dispatch(updateSortField(sortField)),
- updateGroupDetail: (itemDetail) => dispatch(updateGroupDetail(itemDetail)),
- };
-};
-
-export default compose(
- connect(mapStateToProps, mapDispatchToProps),
- withUserPermissions
-)(WzGroupsTable);
diff --git a/plugins/main/public/controllers/management/components/management/groups/utils/columns-files.js b/plugins/main/public/controllers/management/components/management/groups/utils/columns-files.js
index 46bedb5a08..6174625d70 100644
--- a/plugins/main/public/controllers/management/components/management/groups/utils/columns-files.js
+++ b/plugins/main/public/controllers/management/components/management/groups/utils/columns-files.js
@@ -42,12 +42,14 @@ export default class GroupsFilesColumns {
field: 'filename',
name: 'File',
align: 'left',
+ searchable: true,
sortable: true
},
{
field: 'hash',
name: 'Checksum',
align: 'left',
+ searchable: true,
sortable: true
}
];
diff --git a/plugins/main/public/controllers/management/components/management/ruleset/components/columns.tsx b/plugins/main/public/controllers/management/components/management/ruleset/components/columns.tsx
index 7b43eea337..8b0d25e718 100644
--- a/plugins/main/public/controllers/management/components/management/ruleset/components/columns.tsx
+++ b/plugins/main/public/controllers/management/components/management/ruleset/components/columns.tsx
@@ -142,6 +142,13 @@ export default class RulesetColumns {
align: 'left',
sortable: true,
},
+ {
+ field: 'relative_dirname',
+ name: 'Path',
+ align: 'left',
+ sortable: true,
+ width: '10%',
+ },
{
name: 'Actions',
align: 'left',
diff --git a/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-suggestions.ts b/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-suggestions.ts
index 84e4115185..aa6486bdb4 100644
--- a/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-suggestions.ts
+++ b/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-suggestions.ts
@@ -1,137 +1,208 @@
+import { SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT } from '../../../../../../../common/constants';
import { WzRequest } from '../../../../../../react-services/wz-request';
-const rulesItems = [
- {
- type: 'params',
- label: 'status',
- description: 'Filters the rules by status.',
- values: ['enabled', 'disabled']
+const rulesItems = {
+ field(currentValue) {
+ return [
+ { label: 'id', description: 'filter by ID' },
+ { label: 'filename', description: 'filter by filename' },
+ { label: 'gdpr', description: 'filter by GDPR requirement' },
+ { label: 'gpg13', description: 'filter by GPG requirement' },
+ { label: 'groups', description: 'filter by group' },
+ { label: 'hipaa', description: 'filter by HIPAA requirement' },
+ { label: 'level', description: 'filter by level' },
+ { label: 'mitre', description: 'filter by MITRE ATT&CK requirement' },
+ { label: 'nist_800_53', description: 'filter by NIST requirement' },
+ { label: 'pci_dss', description: 'filter by PCI DSS requirement' },
+ { label: 'relative_dirname', description: 'filter by relative dirname' },
+ { label: 'status', description: 'filter by status' },
+ { label: 'tsc', description: 'filter by TSC requirement' },
+ ];
},
- {
- 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'
+ value: async (currentValue, { field }) => {
+ try {
+ switch (field) {
+ case 'id': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `id~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/rules', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items.map(label => ({
+ label: label[field],
+ }));
}
- });
- 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;
+ case 'status': {
+ return ['enabled', 'disabled'].map(label => ({ label }));
+ }
+ case 'groups': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/rules/groups', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ case 'level': {
+ return [...Array(16).keys()].map(label => ({ label }));
+ }
+ case 'filename': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/rules/files', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items?.map(item => ({
+ label: item[field],
+ }));
+ }
+ case 'relative_dirname': {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/rules', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items.map(item => ({
+ label: item[field],
+ }));
+ }
+ case 'hipaa': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq(
+ 'GET',
+ '/rules/requirement/hipaa',
+ { params: filter },
+ );
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ case 'gdpr': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq(
+ 'GET',
+ '/rules/requirement/gdpr',
+ { params: filter },
+ );
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ case 'nist_800_53': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq(
+ 'GET',
+ '/rules/requirement/nist-800-53',
+ { params: filter },
+ );
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ case 'gpg13': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq(
+ 'GET',
+ '/rules/requirement/gpg13',
+ { params: filter },
+ );
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ case 'pci_dss': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq(
+ 'GET',
+ '/rules/requirement/pci_dss',
+ { params: filter },
+ );
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ case 'tsc': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq(
+ 'GET',
+ '/rules/requirement/tsc',
+ { params: filter },
+ );
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ case 'mitre': {
+ const filter = {
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ ...(currentValue ? { search: currentValue } : {}),
+ };
+ const result = await WzRequest.apiReq(
+ 'GET',
+ '/rules/requirement/mitre',
+ { params: filter },
+ );
+ return result?.data?.data?.affected_items.map(label => ({ label }));
+ }
+ default:
+ return [];
+ }
+ } catch (error) {
+ return [];
}
},
- {
- 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;
- }
+};
+
+const rulesFiles = {
+ field(currentValue) {
+ return [
+ { label: 'filename', description: 'filter by filename' },
+ { label: 'relative_dirname', description: 'filter by relative dirname' },
+ ];
},
- {
- 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;
+ value: async (currentValue, { field }) => {
+ try {
+ const filter = {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue ? { q: `${field}~${currentValue}` } : {}),
+ };
+ const result = await WzRequest.apiReq('GET', '/rules/files', {
+ params: filter,
+ });
+ return result?.data?.data?.affected_items?.map(item => ({
+ label: item[field],
+ }));
+ } catch (error) {
+ return [];
}
- }
-];
-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,
diff --git a/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-table.tsx b/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-table.tsx
index 229637f7a1..6ebfa3980a 100644
--- a/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-table.tsx
+++ b/plugins/main/public/controllers/management/components/management/ruleset/components/ruleset-table.tsx
@@ -39,6 +39,41 @@ import {
import apiSuggestsItems from './ruleset-suggestions';
+const searchBarWQLOptions = {
+ searchTermFields: [
+ 'id',
+ 'description',
+ 'filename',
+ 'gdpr',
+ 'gpg13',
+ 'groups',
+ 'level',
+ 'mitre',
+ 'nist_800_53',
+ 'pci_dss',
+ 'relative_dirname',
+ 'tsc',
+ ],
+ filterButtons: [
+ {
+ id: 'relative-dirname',
+ input: 'relative_dirname=etc/rules',
+ label: 'Custom rules',
+ },
+ ],
+};
+
+const searchBarWQLOptionsFiles = {
+ searchTermFields: ['filename', 'relative_dirname'],
+ filterButtons: [
+ {
+ id: 'relative-dirname',
+ input: 'relative_dirname=etc/rules',
+ label: 'Custom rules',
+ },
+ ],
+};
+
/***************************************
* Render tables
*/
@@ -54,19 +89,20 @@ const FilesTable = ({
);
@@ -88,20 +124,21 @@ const RulesFlyoutTable = ({
<>
{isFlyoutVisible && (
diff --git a/plugins/main/public/controllers/management/components/management/ruleset/views/rule-info.tsx b/plugins/main/public/controllers/management/components/management/ruleset/views/rule-info.tsx
index 866921553c..a95208846b 100644
--- a/plugins/main/public/controllers/management/components/management/ruleset/views/rule-info.tsx
+++ b/plugins/main/public/controllers/management/components/management/ruleset/views/rule-info.tsx
@@ -323,7 +323,7 @@ export default class WzRuleInfo extends Component {
>
- this.setNewFiltersAndBack([{ field: 'rule_ids', value: id }])
+ this.setNewFiltersAndBack({ q: `id=${id}` })
}
>
{id}
@@ -340,7 +340,7 @@ export default class WzRuleInfo extends Component {
>
- this.setNewFiltersAndBack([{ field: 'level', value: level }])
+ this.setNewFiltersAndBack({ q: `level=${level}` })
}
>
{level}
@@ -354,9 +354,7 @@ export default class WzRuleInfo extends Component {
- this.setNewFiltersAndBack([
- { field: 'filename', value: file },
- ])
+ this.setNewFiltersAndBack({ q: `filename=${file}` })
}
>
{file}
@@ -370,9 +368,7 @@ export default class WzRuleInfo extends Component {
- this.setNewFiltersAndBack([
- { field: 'relative_dirname', value: path },
- ])
+ this.setNewFiltersAndBack({ q: `relative_dirname=${path}` })
}
>
{path}
@@ -486,7 +482,7 @@ export default class WzRuleInfo extends Component {
- this.setNewFiltersAndBack([{ field: 'group', value: group }])
+ this.setNewFiltersAndBack({ q: `groups=${group}` })
}
>
- this.setNewFiltersAndBack([{ field: key, value: element }])
+ this.setNewFiltersAndBack({ q: `${key}=${element}` })
}
>
@@ -628,9 +624,9 @@ export default class WzRuleInfo extends Component {
- this.setNewFiltersAndBack([
- { field: 'mitre', value: this.state.mitreIds[index] },
- ])
+ this.setNewFiltersAndBack({
+ q: `mitre=${this.state.mitreIds[index]}`,
+ })
}
>
@@ -857,7 +853,7 @@ export default class WzRuleInfo extends Component {
{this.state.currentRuleInfo?.filename && (
{
- return field !== undefined ? field : '-';
+const searchBarWQLOptions = {
+ implicitQuery: {
+ query: 'id!=000',
+ conjunction: ';',
+ },
};
export class AgentSelectionTable extends Component {
constructor(props) {
super(props);
this.state = {
- itemIdToSelectedMap: {},
- itemIdToOpenActionsPopoverMap: {},
- sortedColumn: 'title',
- itemsPerPage: 10,
- pageIndex: 0,
- totalItems: 0,
- isLoading: false,
- sortDirection: 'asc',
- sortField: 'id',
- agents: [],
- selectedOptions: [],
- filters: []
+ filters: { default: { q: 'id!=000' } },
};
this.columns = [
{
- id: 'id',
- label: 'ID',
- alignment: LEFT_ALIGNMENT,
+ field: 'id',
+ name: 'ID',
width: '60px',
- mobileOptions: {
- show: true,
- },
- isSortable: true,
+ searchable: true,
+ sortable: true,
},
{
- id: 'name',
- label: 'Name',
- alignment: LEFT_ALIGNMENT,
- mobileOptions: {
- show: true,
- },
- isSortable: true
+ field: 'name',
+ name: 'Name',
+ searchable: true,
+ sortable: true,
},
{
- id: 'group',
- label: 'Group',
- alignment: LEFT_ALIGNMENT,
- mobileOptions: {
- show: false,
- },
- isSortable: true,
- render: groups => this.renderGroups(groups)
+ field: 'group',
+ name: 'Group',
+ sortable: true,
+ searchable: true,
+ render: groups => this.renderGroups(groups),
},
{
- id: 'version',
- label: 'Version',
+ field: 'version',
+ name: 'Version',
width: '80px',
- alignment: LEFT_ALIGNMENT,
- mobileOptions: {
- show: true,
- },
- isSortable: true,
+ searchable: true,
+ sortable: true,
},
{
- id: 'os',
- label: 'Operating system',
- alignment: LEFT_ALIGNMENT,
- mobileOptions: {
- show: false,
- },
- isSortable: true,
- render: os => this.addIconPlatformRender(os)
+ field: 'os.name,os.version',
+ composeField: ['os.name', 'os.version'],
+ name: 'Operating system',
+ sortable: true,
+ searchable: true,
+ render: (field, agentData) => this.addIconPlatformRender(agentData),
},
{
- id: 'status',
- label: 'Status',
- alignment: LEFT_ALIGNMENT,
- mobileOptions: {
- show: true,
- },
- isSortable: true,
+ field: 'status',
+ name: 'Status',
+ searchable: true,
+ sortable: true,
width: 'auto',
- render: status => ,
+ render: status => (
+
+ ),
},
];
- this.suggestions = [
- { type: 'q', label: 'status', description: 'Filter by agent connection status', operators: ['=', '!=',], values: UI_ORDER_AGENT_STATUS },
- { type: 'q', label: 'os.platform', description: 'Filter by operating system platform', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('os.platform', value, { q: 'id!=000'})},
- { type: 'q', label: 'ip', description: 'Filter by agent IP address', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('ip', value, { q: 'id!=000'})},
- { type: 'q', label: 'name', description: 'Filter by agent name', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('name', value, { q: 'id!=000'})},
- { type: 'q', label: 'id', description: 'Filter by agent id', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('id', value, { q: 'id!=000'})},
- { type: 'q', label: 'group', description: 'Filter by agent group', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('group', value, { q: 'id!=000'})},
- { type: 'q', label: 'node_name', description: 'Filter by node name', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('node_name', value, { q: 'id!=000'})},
- { type: 'q', label: 'manager', description: 'Filter by manager', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('manager', value, { q: 'id!=000'})},
- { type: 'q', label: 'version', description: 'Filter by agent version', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('version', value, { q: 'id!=000'})},
- { type: 'q', label: 'configSum', description: 'Filter by agent config sum', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('configSum', value, { q: 'id!=000'})},
- { type: 'q', label: 'mergedSum', description: 'Filter by agent merged sum', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('mergedSum', value, { q: 'id!=000'})},
- { type: 'q', label: 'dateAdd', description: 'Filter by add date', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('dateAdd', value, { q: 'id!=000'})},
- { type: 'q', label: 'lastKeepAlive', description: 'Filter by last keep alive', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('lastKeepAlive', value, { q: 'id!=000'})},
- ];
- }
-
- onChangeItemsPerPage = async itemsPerPage => {
- this._isMounted && this.setState({ itemsPerPage }, async () => await this.getItems());
- };
-
- onChangePage = async pageIndex => {
- this._isMounted && this.setState({ pageIndex }, async () => await this.getItems());
- };
-
- async componentDidMount() {
- this._isMounted = true;
- const tmpSelectedAgents = {};
- if(!store.getState().appStateReducers.currentAgentData.id){
- tmpSelectedAgents[store.getState().appStateReducers.currentAgentData.id] = true;
- }
- this._isMounted && this.setState({itemIdToSelectedMap: this.props.selectedAgents});
- await this.getItems();
- }
-
- componentWillUnmount(){
- this._isMounted = false;
- }
-
- async componentDidUpdate(prevProps, prevState) {
- if(!(_.isEqual(prevState.filters,this.state.filters))){
- await this.getItems();
- }
- }
-
- getArrayFormatted(arrayText) {
- try {
- const stringText = arrayText.toString();
- const splitString = stringText.split(',');
- return splitString.join(', ');
- } catch (error) {
- const options = {
- context: `${AgentSelectionTable.name}.getArrayFormatted`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.UI,
- error: {
- error: error,
- message: error.message || error,
- title: error.name || error,
- },
- };
-
- getErrorOrchestrator().handleError(options);
- return arrayText;
- }
- }
-
- async getItems() {
- try {
- this._isMounted && this.setState({ isLoading: true });
- const rawData = await WzRequest.apiReq('GET', '/agents', { params: this.buildFilter() });
- const data = (((rawData || {}).data || {}).data || {}).affected_items;
- const totalItems = (((rawData || {}).data || {}).data || {}).total_affected_items;
- const formattedData = data.map((item, id) => {
- return {
- id: item.id,
- name: item.name,
- version: item.version !== undefined ? item.version.split(' ')[1] : '-',
- os: item.os || '-',
- status: item.status,
- group: item.group || '-',
- };
- });
- this._isMounted && this.setState({ agents: formattedData, totalItems, isLoading: false });
- } catch (error) {
- this._isMounted && this.setState({ isLoading: false });
- const options = {
- context: `${AgentSelectionTable.name}.getItems`,
- level: UI_LOGGER_LEVELS.ERROR,
- severity: UI_ERROR_SEVERITIES.BUSINESS,
- error: {
- error: error,
- message: error.message || error,
- title: error.name || error,
- },
- };
-
- getErrorOrchestrator().handleError(options);
- }
- }
-
- buildFilter() {
- const { itemsPerPage, pageIndex, filters } = this.state;
- const filter = {
- ...filtersToObject(filters),
- offset: (pageIndex * itemsPerPage) || 0,
- limit: pageIndex * itemsPerPage + itemsPerPage,
- ...this.buildSortFilter()
- };
- filter.q = !filter.q ? `id!=000` : `id!=000;${filter.q}`;
- return filter;
- }
-
- buildSortFilter() {
- const { sortDirection, sortField } = this.state;
- const sortFilter = {};
- if (sortField) {
- const direction = sortDirection === 'asc' ? '+' : '-';
- sortFilter['sort'] = direction + (sortField === 'os'? 'os.name,os.version' : sortField);
- }
-
- return sortFilter;
- }
-
- onSort = async prop => {
- const sortField = prop;
- const sortDirection =
- this.state.sortField === prop && this.state.sortDirection === 'asc'
- ? 'desc'
- : this.state.sortDirection === 'asc'
- ? 'desc'
- : 'asc';
-
- this._isMounted && this.setState({ sortField, sortDirection }, async () => await this.getItems());
- };
-
- toggleItem = itemId => {
- this._isMounted && this.setState(previousState => {
- const newItemIdToSelectedMap = {
- [itemId]: !previousState.itemIdToSelectedMap[itemId],
- };
-
- return {
- itemIdToSelectedMap: newItemIdToSelectedMap,
- };
- });
- };
-
- toggleAll = () => {
- const allSelected = this.areAllItemsSelected();
- const newItemIdToSelectedMap = {};
- this.state.agents.forEach(item => (newItemIdToSelectedMap[item.id] = !allSelected));
- this._isMounted && this.setState({
- itemIdToSelectedMap: newItemIdToSelectedMap,
- });
- };
-
- isItemSelected = itemId => {
- return this.state.itemIdToSelectedMap[itemId];
- };
-
- areAllItemsSelected = () => {
- const indexOfUnselectedItem = this.state.agents.findIndex(item => !this.isItemSelected(item.id));
- return indexOfUnselectedItem === -1;
- };
-
- areAnyRowsSelected = () => {
- return (
- Object.keys(this.state.itemIdToSelectedMap).findIndex(id => {
- return this.state.itemIdToSelectedMap[id];
- }) !== -1
- );
- };
-
- togglePopover = itemId => {
- this._isMounted && this.setState(previousState => {
- const newItemIdToOpenActionsPopoverMap = {
- ...previousState.itemIdToOpenActionsPopoverMap,
- [itemId]: !previousState.itemIdToOpenActionsPopoverMap[itemId],
- };
-
- return {
- itemIdToOpenActionsPopoverMap: newItemIdToOpenActionsPopoverMap,
- };
- });
- };
-
- closePopover = itemId => {
- // only update the state if this item's popover is open
- if (this.isPopoverOpen(itemId)) {
- this._isMounted && this.setState(previousState => {
- const newItemIdToOpenActionsPopoverMap = {
- ...previousState.itemIdToOpenActionsPopoverMap,
- [itemId]: false,
- };
-
- return {
- itemIdToOpenActionsPopoverMap: newItemIdToOpenActionsPopoverMap,
- };
- });
- }
- };
-
- isPopoverOpen = itemId => {
- return this.state.itemIdToOpenActionsPopoverMap[itemId];
- };
-
- renderSelectAll = mobile => {
- if (!this.state.isLoading && this.state.agents.length) {
- return (
-
- );
- }
- };
-
- getTableMobileSortItems() {
- const items = [];
- this.columns.forEach(column => {
- if (column.isCheckbox || !column.isSortable) {
- return;
- }
- items.push({
- name: column.label,
- key: column.id,
- onSort: this.onSort.bind(this, column.id),
- isSorted: this.state.sortField === column.id,
- isSortAscending: this.state.sortDirection === 'asc',
- });
- });
- return items.length ? items : null;
- }
-
- renderHeaderCells() {
- const headers = [];
-
- this.columns.forEach((column, columnIndex) => {
- if (column.isCheckbox) {
- headers.push(
-
-
- );
- } else {
- headers.push(
-
- {column.label}
-
- );
- }
- });
- return headers.length ? headers : null;
- }
-
- renderRows() {
- const renderRow = item => {
- const cells = this.columns.map(column => {
- const cell = item[column.id];
-
- let child;
-
- if (column.isCheckbox) {
- return (
-
- {}}
- type="inList"
- />
-
- );
- }
-
- if (column.render) {
- child = column.render(item[column.id]);
- } else {
- child = cell;
- }
-
- return (
-
- {child}
-
- );
- });
-
- return (
- await this.selectAgentAndApply(item.id)}
- hasActions={true}
- >
- {cells}
-
- );
- };
-
- const rows = [];
-
- for (
- let itemIndex = (this.state.pageIndex * this.state.itemsPerPage) % this.state.itemsPerPage;
- itemIndex <
- ((this.state.pageIndex * this.state.itemsPerPage) % this.state.itemsPerPage) +
- this.state.itemsPerPage && this.state.agents[itemIndex];
- itemIndex++
- ) {
- const item = this.state.agents[itemIndex];
- rows.push(renderRow(item));
- }
-
- return rows;
}
- renderFooterCells() {
- const footers = [];
-
- const items = this.state.agents;
- const pagination = {
- pageIndex: this.state.pageIndex,
- pageSize: this.state.itemsPerPage,
- totalItemCount: this.state.totalItems,
- pageSizeOptions: [10, 25, 50, 100]
- };
-
- this.columns.forEach(column => {
- const footer = this.getColumnFooter(column, { items, pagination });
- if (column.mobileOptions && column.mobileOptions.only) {
- return; // exclude columns that only exist for mobile headers
- }
-
- if (footer) {
- footers.push(
-
- {footer}
-
- );
- } else {
- footers.push(
-
- {undefined}
-
- );
- }
- });
- return footers;
- }
-
- getColumnFooter = (column, { items, pagination }) => {
- if (column.footer === null) {
- return null;
- }
- if (column.footer) {
- return column.footer;
- }
-
- return undefined;
- };
-
- async onQueryChange(result) {
- this._isMounted &&
- this.setState({ isLoading: true, ...result }, async () => {
- await this.getItems();
- });
- }
-
- getSelectedItems(){
- return Object.keys(this.state.itemIdToSelectedMap).filter(x => {
- return (this.state.itemIdToSelectedMap[x] === true)
- })
- }
-
- unselectAgents(){
- this._isMounted && this.setState({itemIdToSelectedMap: {}});
+ unselectAgents() {
store.dispatch(updateCurrentAgentData({}));
this.props.removeAgentsFilter();
}
- getSelectedCount(){
- return this.getSelectedItems().length;
- }
-
- async selectAgentAndApply(agentID){
- try{
- const data = await WzRequest.apiReq('GET', '/agents', { params: { q: 'id=' + agentID}});
- const formattedData = data.data.data.affected_items[0] //TODO: do it correctly
+ async selectAgentAndApply(agentID) {
+ try {
+ const data = await WzRequest.apiReq('GET', '/agents', {
+ params: { q: 'id=' + agentID },
+ });
+ const formattedData = data?.data?.data?.affected_items?.[0];
store.dispatch(updateCurrentAgentData(formattedData));
this.props.updateAgentSearch([agentID]);
- } catch(error) {
+ } catch (error) {
store.dispatch(updateCurrentAgentData({}));
this.props.removeAgentsFilter(true);
const options = {
@@ -546,52 +115,42 @@ export class AgentSelectionTable extends Component {
}
}
- showContextMenu(id){
- this._isMounted && this.setState({contextMenuId: id})
- }
+ addIconPlatformRender(agent) {
+ let icon = '';
+ const os = agent?.os || {};
- addIconPlatformRender(os) {
- if(typeof os === "string" ){ return os};
- let icon = false;
-
- if (((os || {}).uname || '').includes('Linux')) {
+ if ((os?.uname || '').includes('Linux')) {
icon = 'linux';
- } else if ((os || {}).platform === 'windows') {
+ } else if (os?.platform === 'windows') {
icon = 'windows';
- } else if ((os || {}).platform === 'darwin') {
+ } else if (os?.platform === 'darwin') {
icon = 'apple';
}
- const os_name =
- checkField((os || {}).name) +
- ' ' +
- checkField((os || {}).version);
+ const os_name = `${agent?.os?.name || ''} ${agent?.os?.version || ''}`;
+
return (
-
- {' '}
- {os_name === '--' ? '-' : os_name}
-
+
+
+
+ {' '}
+ {os_name.trim() || '-'}
+
);
}
- filterGroupBadge = (group) => {
- const { filters } = this.state;
- let auxFilters = filters.map( filter => filter.value.match(/group=(.*S?)/)[1] );
- if (filters.length > 0) {
- !auxFilters.includes(group) ?
- this.setState({
- filters: [...filters, {field: "q", value: `group=${group}`}],
- }) : false;
- } else {
- this.setState({
- filters: [...filters, {field: "q", value: `group=${group}`}],
- })
- }
- }
+ filterGroupBadge = group => {
+ this.setState({
+ filters: {
+ default: { q: 'id!=000' },
+ q: `group=${group}`,
+ },
+ });
+ };
- renderGroups(groups){
+ renderGroups(groups) {
return Array.isArray(groups) ? (
- ) : groups
+ {...this.props}
+ />
+ ) : (
+ groups
+ );
}
render() {
- const pagination = {
- pageIndex: this.state.pageIndex,
- pageSize: this.state.itemsPerPage,
- totalItemCount: this.state.totalItems,
- pageCount:
- this.state.totalItems % this.state.itemsPerPage === 0
- ? this.state.totalItems / this.state.itemsPerPage
- : parseInt(this.state.totalItems / this.state.itemsPerPage) + 1,
- };
const selectedAgent = store.getState().appStateReducers.currentAgentData;
+ const getRowProps = (item, idx) => {
+ return {
+ 'data-test-subj': `explore-agent-${idx}`,
+ className: 'customRowClass',
+ onClick: () => this.selectAgentAndApply(item.id),
+ };
+ };
+
return (
-
-
- this.setState({filters, pageIndex: 0})}
- placeholder="Filter or search agent"
- />
-
-
-
{selectedAgent && Object.keys(selectedAgent).length > 0 && (
-
+
{/* agent name (agent id) Unpin button right aligned, require justifyContent="flexEnd" in the EuiFlexGroup */}
-
-
+
+
{selectedAgent.name} ({selectedAgent.id})
-
+
this.unselectAgents()}
- iconType="pinFilled"
- aria-label="unpin agent"
+ iconType='pinFilled'
+ aria-label='unpin agent'
/>
-
+
)}
-
-
-
-
-
-
-
-
-
-
- {this.renderHeaderCells()}
- {(this.state.agents.length && (
-
- {this.renderRows()}
-
- )) || (
-
-
-
- {this.state.isLoading ? 'Loading agents' : 'No results found'}
-
-
-
- )}
-
-
-
-
-
{
+ return {
+ ...item,
+ /*
+ The agent version contains the Wazuh word, this get the string starting with
+ v
+ */
+ ...(typeof item.version === 'string'
+ ? { version: item.version.match(/(v\d.+)/)?.[1] }
+ : { version: '-' }),
+ };
+ }}
+ rowProps={getRowProps}
+ filters={this.state.filters}
+ searchTable
+ searchBarWQL={{
+ options: searchBarWQLOptions,
+ suggestions: {
+ field(currentValue) {
+ return [
+ { label: 'id', description: 'filter by id' },
+ { label: 'group', description: 'filter by group' },
+ { label: 'name', description: 'filter by name' },
+ {
+ label: 'os.name',
+ description: 'filter by operating system name',
+ },
+ {
+ label: 'os.version',
+ description: 'filter by operating system version',
+ },
+ { label: 'status', description: 'filter by status' },
+ { label: 'version', description: 'filter by version' },
+ ];
+ },
+ value: async (currentValue, { field }) => {
+ try {
+ switch (field) {
+ case 'status':
+ return UI_ORDER_AGENT_STATUS.map(status => ({
+ label: status,
+ }));
+ default: {
+ const response = await WzRequest.apiReq(
+ 'GET',
+ '/agents',
+ {
+ params: {
+ distinct: true,
+ limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT,
+ select: field,
+ sort: `+${field}`,
+ ...(currentValue
+ ? {
+ q: `${searchBarWQLOptions.implicitQuery.query}${searchBarWQLOptions.implicitQuery.conjunction}${field}~${currentValue}`,
+ }
+ : {
+ q: `${searchBarWQLOptions.implicitQuery.query}`,
+ }),
+ },
+ },
+ );
+ if (field === 'group') {
+ /* the group field is returned as an string[],
+ example: ['group1', 'group2']
+
+ Due the API request done to get the distinct values for the groups is
+ not returning the exepected values, as workaround, the values are
+ extracted in the frontend using the returned results.
+
+ This API request to get the distint values of groups doesn't
+ return the unique values for the groups, else the unique combination
+ of groups.
+ */
+ return response?.data?.data.affected_items
+ .map(item => getLodash(item, field))
+ .flat()
+ .filter(
+ (item, index, array) =>
+ array.indexOf(item) === index,
+ )
+ .sort()
+ .map(group => ({ label: group }));
+ }
+ return response?.data?.data.affected_items.map(item => ({
+ label: getLodash(item, field),
+ }));
+ }
+ }
+ } catch (error) {
+ return [];
+ }
+ },
+ },
+ }}
/>
);
diff --git a/plugins/main/public/controllers/overview/components/stats.js b/plugins/main/public/controllers/overview/components/stats.js
index a9b1786250..0f8a7bfab4 100644
--- a/plugins/main/public/controllers/overview/components/stats.js
+++ b/plugins/main/public/controllers/overview/components/stats.js
@@ -33,11 +33,11 @@ export const Stats = withErrorBoundary (class Stats extends Component {
goToAgents(status) {
if(status){
sessionStorage.setItem(
- 'agents_preview_selected_options',
- JSON.stringify([{field: 'q', value: `status=${status}`}])
+ 'wz-agents-overview-table-filter',
+ JSON.stringify({q: `id!=000;status=${status}`})
);
- }else if(sessionStorage.getItem('agents_preview_selected_options')){
- sessionStorage.removeItem('agents_preview_selected_options');
+ }else if(sessionStorage.getItem('wz-agents-overview-table-filter')){
+ sessionStorage.removeItem('wz-agents-overview-table-filter');
}
window.location.href = '#/agents-preview';
}
diff --git a/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.scss b/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.scss
index 55dd4092fa..a71216b6a6 100644
--- a/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.scss
+++ b/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.scss
@@ -18,10 +18,6 @@
margin-right: 10px;
}
-.euiCard__content .euiCard__titleButton {
- text-decoration: none !important;
-}
-
.cardText {
font-style: normal;
font-weight: 700;
@@ -56,4 +52,4 @@
.cardsCallOut {
margin-top: 16px;
-}
+}
\ No newline at end of file
diff --git a/plugins/main/public/controllers/register-agent/containers/steps/steps.scss b/plugins/main/public/controllers/register-agent/containers/steps/steps.scss
index 337cc41298..005ccb6379 100644
--- a/plugins/main/public/controllers/register-agent/containers/steps/steps.scss
+++ b/plugins/main/public/controllers/register-agent/containers/steps/steps.scss
@@ -6,50 +6,51 @@
letter-spacing: 0.6px;
flex-direction: row;
}
-}
-
-.stepSubtitleServerAddress {
- font-style: normal;
- font-weight: 400;
- font-size: 14px;
- line-height: 24px;
- margin-bottom: 9px;
-}
-
-.stepSubtitle {
- font-style: normal;
- font-weight: 400;
- font-size: 14px;
- line-height: 24px;
- margin-bottom: 20px;
-}
-
-.titleAndIcon {
- display: flex;
- flex-direction: row;
-}
-
-.warningForAgentName {
- margin-top: 10px;
-}
-
-.euiToolTipAnchor {
- margin-left: 7px;
-}
-
-.subtitleAgentName {
- flex-direction: 'row';
- font-style: 'normal';
- font-weight: 700;
- font-size: '12px';
- line-height: '20px';
- color: '#343741';
-}
-
-.euiStep__titleWrapper {
- align-items: center;
-}
-
-.euiButtonEmpty .euiButtonEmpty__content {
- padding: 0;
-}
+
+
+ .stepSubtitleServerAddress {
+ font-style: normal;
+ font-weight: 400;
+ font-size: 14px;
+ line-height: 24px;
+ margin-bottom: 9px;
+ }
+
+ .stepSubtitle {
+ font-style: normal;
+ font-weight: 400;
+ font-size: 14px;
+ line-height: 24px;
+ margin-bottom: 20px;
+ }
+
+ .titleAndIcon {
+ display: flex;
+ flex-direction: row;
+ }
+
+ .warningForAgentName {
+ margin-top: 10px;
+ }
+
+ .euiToolTipAnchor {
+ margin-left: 7px;
+ }
+
+ .subtitleAgentName {
+ flex-direction: 'row';
+ font-style: 'normal';
+ font-weight: 700;
+ font-size: '12px';
+ line-height: '20px';
+ color: '#343741';
+ }
+
+ .euiStep__titleWrapper {
+ align-items: center;
+ }
+
+ .euiButtonEmpty .euiButtonEmpty__content {
+ padding: 0;
+ }
+}
\ No newline at end of file
diff --git a/plugins/main/public/react-services/reporting.js b/plugins/main/public/react-services/reporting.js
index b74ed090ff..4fa1dee29d 100644
--- a/plugins/main/public/react-services/reporting.js
+++ b/plugins/main/public/react-services/reporting.js
@@ -89,13 +89,15 @@ export class ReportingService {
}
const appliedFilters = await this.visHandlers.getAppliedFilters(syscollectorFilters);
-
+ const dataplugin = await getDataPlugin();
+ const serverSideQuery = dataplugin.query.getOpenSearchQuery();
const array = await this.vis2png.checkArray(visualizationIDList);
const browserTimezone = moment.tz.guess(true);
const data = {
array,
+ serverSideQuery, // Used for applying the same filters on the server side requests
filters: appliedFilters.filters,
time: appliedFilters.time,
searchBar: appliedFilters.searchBar,
diff --git a/plugins/main/server/controllers/wazuh-reporting.ts b/plugins/main/server/controllers/wazuh-reporting.ts
index 51cd76ea6f..5a13636cd1 100644
--- a/plugins/main/server/controllers/wazuh-reporting.ts
+++ b/plugins/main/server/controllers/wazuh-reporting.ts
@@ -36,7 +36,7 @@ interface AgentsFilter {
}
export class WazuhReportingCtrl {
- constructor() {}
+ constructor() { }
/**
* This do format to filters
* @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
@@ -70,22 +70,21 @@ export class WazuhReportingCtrl {
const { negate, key, value, params, type } = filters[i].meta;
str += `${negate ? 'NOT ' : ''}`;
str += `${key}: `;
- str += `${
- type === 'range'
- ? `${params.gte}-${params.lt}`
- : type === 'phrases'
- ? '(' + params.join(" OR ") + ')'
- : type === 'exists'
- ? '*'
- : !!value
- ? value
- : (params || {}).query
- }`;
+ str += `${type === 'range'
+ ? `${params.gte}-${params.lt}`
+ : type === 'phrases'
+ ? '(' + params.join(" OR ") + ')'
+ : type === 'exists'
+ ? '*'
+ : !!value
+ ? value
+ : (params || {}).query
+ }`;
str += `${i === len - 1 ? '' : ' AND '}`;
}
if (searchBar) {
- str += ` AND (${ searchBar})`;
+ str += ` AND (${searchBar})`;
}
agentsFilter.agentsText = agentsList.map((filter) => filter.meta.value).join(',');
@@ -211,8 +210,8 @@ export class WazuhReportingCtrl {
plainData[key] =
Array.isArray(data[key]) && typeof data[key][0] !== 'object'
? data[key].map((x) => {
- return typeof x === 'object' ? JSON.stringify(x) : x + '\n';
- })
+ return typeof x === 'object' ? JSON.stringify(x) : x + '\n';
+ })
: data[key];
} else if (Array.isArray(data[key]) && typeof data[key][0] === 'object') {
tableData[key] = data[key];
@@ -229,7 +228,7 @@ export class WazuhReportingCtrl {
title: (section.options || {}).hideHeader
? ''
: (section.tabs || [])[tab] ||
- (section.isGroupConfig ? ((section.labels || [])[0] || [])[tab] : ''),
+ (section.isGroupConfig ? ((section.labels || [])[0] || [])[tab] : ''),
columns: ['', ''],
type: 'config',
rows: this.getConfigRows(plainData, (section.labels || [])[0]),
@@ -247,10 +246,10 @@ export class WazuhReportingCtrl {
typeof x[key] !== 'object'
? x[key]
: Array.isArray(x[key])
- ? x[key].map((x) => {
+ ? x[key].map((x) => {
return x + '\n';
})
- : JSON.stringify(x[key])
+ : JSON.stringify(x[key])
);
}
while (row.length < columns.length) {
@@ -291,6 +290,7 @@ export class WazuhReportingCtrl {
browserTimezone,
searchBar,
filters,
+ serverSideQuery,
time,
tables,
section,
@@ -327,7 +327,7 @@ export class WazuhReportingCtrl {
apiId,
new Date(from).getTime(),
new Date(to).getTime(),
- sanitizedFilters,
+ serverSideQuery,
agentsFilter,
indexPatternTitle,
agents
@@ -356,7 +356,7 @@ export class WazuhReportingCtrl {
} catch (error) {
return ErrorResponse(error.message || error, 5029, 500, response);
}
- },({body:{ agents }, params: { moduleID }}) => `wazuh-module-${agents ? `agents-${agents}` : 'overview'}-${moduleID}-${this.generateReportTimestamp()}.pdf`)
+ }, ({ body: { agents }, params: { moduleID } }) => `wazuh-module-${agents ? `agents-${agents}` : 'overview'}-${moduleID}-${this.generateReportTimestamp()}.pdf`)
/**
* Create a report for the groups
@@ -365,7 +365,7 @@ export class WazuhReportingCtrl {
* @param {Object} response
* @returns {*} reports list or ErrorResponse
*/
- createReportsGroups = this.checkReportsUserDirectoryIsValidRouteDecorator(async(
+ createReportsGroups = this.checkReportsUserDirectoryIsValidRouteDecorator(async (
context: RequestHandlerContext,
request: OpenSearchDashboardsRequest,
response: OpenSearchDashboardsResponseFactory
@@ -486,10 +486,10 @@ export class WazuhReportingCtrl {
typeof x[key] !== 'object'
? x[key]
: Array.isArray(x[key])
- ? x[key].map((x) => {
+ ? x[key].map((x) => {
return x + '\n';
})
- : JSON.stringify(x[key])
+ : JSON.stringify(x[key])
);
});
return row;
@@ -613,7 +613,7 @@ export class WazuhReportingCtrl {
log('reporting:createReportsGroups', error.message || error);
return ErrorResponse(error.message || error, 5029, 500, response);
}
- }, ({params: { groupID }}) => `wazuh-group-configuration-${groupID}-${this.generateReportTimestamp()}.pdf`)
+ }, ({ params: { groupID } }) => `wazuh-group-configuration-${groupID}-${this.generateReportTimestamp()}.pdf`)
/**
* Create a report for the agents
@@ -622,7 +622,7 @@ export class WazuhReportingCtrl {
* @param {Object} response
* @returns {*} reports list or ErrorResponse
*/
- createReportsAgentsConfiguration = this.checkReportsUserDirectoryIsValidRouteDecorator( async (
+ createReportsAgentsConfiguration = this.checkReportsUserDirectoryIsValidRouteDecorator(async (
context: RequestHandlerContext,
request: OpenSearchDashboardsRequest,
response: OpenSearchDashboardsResponseFactory
@@ -745,10 +745,10 @@ export class WazuhReportingCtrl {
typeof x[key] !== 'object'
? x[key]
: Array.isArray(x[key])
- ? x[key].map((x) => {
+ ? x[key].map((x) => {
return x + '\n';
})
- : JSON.stringify(x[key])
+ : JSON.stringify(x[key])
);
});
return row;
@@ -775,13 +775,13 @@ export class WazuhReportingCtrl {
} else {
/*INTEGRITY MONITORING MONITORED DIRECTORIES */
if (conf.matrix) {
- const {directories,diff,synchronization,file_limit,...rest} = agentConfig[agentConfigKey];
+ const { directories, diff, synchronization, file_limit, ...rest } = agentConfig[agentConfigKey];
tables.push(
...this.getConfigTables(rest, section, idx),
- ...(diff && diff.disk_quota ? this.getConfigTables(diff.disk_quota, {tabs:['Disk quota']}, 0 ): []),
- ...(diff && diff.file_size ? this.getConfigTables(diff.file_size, {tabs:['File size']}, 0 ): []),
- ...(synchronization ? this.getConfigTables(synchronization, {tabs:['Synchronization']}, 0 ): []),
- ...(file_limit ? this.getConfigTables(file_limit, {tabs:['File limit']}, 0 ): []),
+ ...(diff && diff.disk_quota ? this.getConfigTables(diff.disk_quota, { tabs: ['Disk quota'] }, 0) : []),
+ ...(diff && diff.file_size ? this.getConfigTables(diff.file_size, { tabs: ['File size'] }, 0) : []),
+ ...(synchronization ? this.getConfigTables(synchronization, { tabs: ['Synchronization'] }, 0) : []),
+ ...(file_limit ? this.getConfigTables(file_limit, { tabs: ['File limit'] }, 0) : []),
);
let diffOpts = [];
Object.keys(section.opts).forEach((x) => {
@@ -860,7 +860,7 @@ export class WazuhReportingCtrl {
log('reporting:createReportsAgentsConfiguration', error.message || error);
return ErrorResponse(error.message || error, 5029, 500, response);
}
- }, ({ params: { agentID }}) => `wazuh-agent-configuration-${agentID}-${this.generateReportTimestamp()}.pdf`)
+ }, ({ params: { agentID } }) => `wazuh-agent-configuration-${agentID}-${this.generateReportTimestamp()}.pdf`)
/**
* Create a report for the agents
@@ -869,14 +869,14 @@ export class WazuhReportingCtrl {
* @param {Object} response
* @returns {*} reports list or ErrorResponse
*/
- createReportsAgentsInventory = this.checkReportsUserDirectoryIsValidRouteDecorator( async (
+ createReportsAgentsInventory = this.checkReportsUserDirectoryIsValidRouteDecorator(async (
context: RequestHandlerContext,
request: OpenSearchDashboardsRequest,
response: OpenSearchDashboardsResponseFactory
) => {
try {
log('reporting:createReportsAgentsInventory', `Report started`, 'info');
- const { searchBar, filters, time, indexPatternTitle, apiId } = request.body;
+ const { searchBar, filters, time, indexPatternTitle, apiId, serverSideQuery } = request.body;
const { agentID } = request.params;
const { from, to } = time || {};
// Init
@@ -924,18 +924,18 @@ export class WazuhReportingCtrl {
columns:
agentOs === 'windows'
? [
- { id: 'name', label: 'Name' },
- { id: 'architecture', label: 'Architecture' },
- { id: 'version', label: 'Version' },
- { id: 'vendor', label: 'Vendor' },
- ]
+ { id: 'name', label: 'Name' },
+ { id: 'architecture', label: 'Architecture' },
+ { id: 'version', label: 'Version' },
+ { id: 'vendor', label: 'Vendor' },
+ ]
: [
- { id: 'name', label: 'Name' },
- { id: 'architecture', label: 'Architecture' },
- { id: 'version', label: 'Version' },
- { id: 'vendor', label: 'Vendor' },
- { id: 'description', label: 'Description' },
- ],
+ { id: 'name', label: 'Name' },
+ { id: 'architecture', label: 'Architecture' },
+ { id: 'version', label: 'Version' },
+ { id: 'vendor', label: 'Vendor' },
+ { id: 'description', label: 'Description' },
+ ],
},
},
{
@@ -946,17 +946,17 @@ export class WazuhReportingCtrl {
columns:
agentOs === 'windows'
? [
- { id: 'name', label: 'Name' },
- { id: 'cmd', label: 'CMD' },
- { id: 'priority', label: 'Priority' },
- { id: 'nlwp', label: 'NLWP' },
- ]
+ { id: 'name', label: 'Name' },
+ { id: 'cmd', label: 'CMD' },
+ { id: 'priority', label: 'Priority' },
+ { id: 'nlwp', label: 'NLWP' },
+ ]
: [
- { id: 'name', label: 'Name' },
- { id: 'euser', label: 'Effective user' },
- { id: 'nice', label: 'Priority' },
- { id: 'state', label: 'State' },
- ],
+ { id: 'name', label: 'Name' },
+ { id: 'euser', label: 'Effective user' },
+ { id: 'nice', label: 'Priority' },
+ { id: 'state', label: 'State' },
+ ],
},
mapResponseItems: (item) =>
agentOs === 'windows' ? item : { ...item, state: ProcessEquivalence[item.state] },
@@ -969,18 +969,18 @@ export class WazuhReportingCtrl {
columns:
agentOs === 'windows'
? [
- { id: 'local_ip', label: 'Local IP address' },
- { id: 'local_port', label: 'Local port' },
- { id: 'process', label: 'Process' },
- { id: 'state', label: 'State' },
- { id: 'protocol', label: 'Protocol' },
- ]
+ { id: 'local_ip', label: 'Local IP address' },
+ { id: 'local_port', label: 'Local port' },
+ { id: 'process', label: 'Process' },
+ { id: 'state', label: 'State' },
+ { id: 'protocol', label: 'Protocol' },
+ ]
: [
- { id: 'local_ip', label: 'Local IP address' },
- { id: 'local_port', label: 'Local port' },
- { id: 'state', label: 'State' },
- { id: 'protocol', label: 'Protocol' },
- ],
+ { id: 'local_ip', label: 'Local IP address' },
+ { id: 'local_port', label: 'Local port' },
+ { id: 'state', label: 'State' },
+ { id: 'protocol', label: 'Protocol' },
+ ],
},
mapResponseItems: (item) => ({
...item,
@@ -1062,6 +1062,15 @@ export class WazuhReportingCtrl {
};
if (time) {
+ // Add Vulnerability Detector filter to the Server Side Query
+ serverSideQuery?.bool?.must?.push?.({
+ match_phrase: {
+ "rule.groups": {
+ query: "vulnerability-detector"
+ }
+ }
+ });
+
await extendedInformation(
context,
printer,
@@ -1070,7 +1079,7 @@ export class WazuhReportingCtrl {
apiId,
from,
to,
- sanitizedFilters + ' AND rule.groups: "vulnerability-detector"',
+ serverSideQuery,
agentsFilter,
indexPatternTitle,
agentID
@@ -1095,7 +1104,7 @@ export class WazuhReportingCtrl {
log('reporting:createReportsAgents', error.message || error);
return ErrorResponse(error.message || error, 5029, 500, response);
}
- }, ({params: { agentID }}) => `wazuh-agent-inventory-${agentID}-${this.generateReportTimestamp()}.pdf`)
+ }, ({ params: { agentID } }) => `wazuh-agent-inventory-${agentID}-${this.generateReportTimestamp()}.pdf`)
/**
* Fetch the reports list
@@ -1194,21 +1203,21 @@ export class WazuhReportingCtrl {
log('reporting:deleteReportByName', error.message || error);
return ErrorResponse(error.message || error, 5032, 500, response);
}
- },(request) => request.params.name)
+ }, (request) => request.params.name)
- checkReportsUserDirectoryIsValidRouteDecorator(routeHandler, reportFileNameAccessor){
+ checkReportsUserDirectoryIsValidRouteDecorator(routeHandler, reportFileNameAccessor) {
return (async (
context: RequestHandlerContext,
request: OpenSearchDashboardsRequest,
response: OpenSearchDashboardsResponseFactory
) => {
- try{
+ try {
const { username, hashUsername } = await context.wazuh.security.getCurrentUser(request, context);
const userReportsDirectoryPath = path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, hashUsername);
const filename = reportFileNameAccessor(request);
const pathFilename = path.join(userReportsDirectoryPath, filename);
log('reporting:checkReportsUserDirectoryIsValidRouteDecorator', `Checking the user ${username}(${hashUsername}) can do actions in the reports file: ${pathFilename}`, 'debug');
- if(!pathFilename.startsWith(userReportsDirectoryPath) || pathFilename.includes('../')){
+ if (!pathFilename.startsWith(userReportsDirectoryPath) || pathFilename.includes('../')) {
log('security:reporting:checkReportsUserDirectoryIsValidRouteDecorator', `User ${username}(${hashUsername}) tried to access to a non user report file: ${pathFilename}`, 'warn');
return response.badRequest({
body: {
@@ -1217,15 +1226,15 @@ export class WazuhReportingCtrl {
});
};
log('reporting:checkReportsUserDirectoryIsValidRouteDecorator', 'Checking the user can do actions in the reports file', 'debug');
- return await routeHandler.bind(this)({...context, wazuhEndpointParams: { hashUsername, filename, pathFilename }}, request, response);
- }catch(error){
+ return await routeHandler.bind(this)({ ...context, wazuhEndpointParams: { hashUsername, filename, pathFilename } }, request, response);
+ } catch (error) {
log('reporting:checkReportsUserDirectoryIsValidRouteDecorator', error.message || error);
return ErrorResponse(error.message || error, 5040, 500, response);
}
})
}
- private generateReportTimestamp(){
+ private generateReportTimestamp() {
return `${(Date.now() / 1000) | 0}`;
}
}
diff --git a/plugins/main/server/lib/reporting/base-query.ts b/plugins/main/server/lib/reporting/base-query.ts
index 09d1f35f50..7e67e541d8 100644
--- a/plugins/main/server/lib/reporting/base-query.ts
+++ b/plugins/main/server/lib/reporting/base-query.ts
@@ -9,45 +9,28 @@
*
* Find more information about this on the LICENSE file.
*/
+
+import { cloneDeep } from 'lodash';
+
export function Base(pattern: string, filters: any, gte: number, lte: number, allowedAgentsFilter: any = null) {
+ const clonedFilter = cloneDeep(filters);
+ clonedFilter?.bool?.must?.push?.({
+ range: {
+ timestamp: {
+ gte: gte,
+ lte: lte,
+ format: 'epoch_millis'
+ }
+ }
+ });
const base = {
- // index: pattern,
-
from: 0,
size: 500,
aggs: {},
sort: [],
script_fields: {},
- query: {
- bool: {
- must: [
- {
- query_string: {
- query: filters,
- analyze_wildcard: true,
- default_field: '*'
- }
- },
- {
- range: {
- timestamp: {
- gte: gte,
- lte: lte,
- format: 'epoch_millis'
- }
- }
- }
- ],
- must_not: []
- }
- }
+ query: clonedFilter
};
- //Add allowed agents filter
- if(allowedAgentsFilter?.query?.bool){
- base.query.bool.minimum_should_match = allowedAgentsFilter.query.bool.minimum_should_match;
- base.query.bool.should = allowedAgentsFilter.query.bool.should;
- }
-
return base;
}
diff --git a/plugins/main/server/lib/reporting/extended-information.ts b/plugins/main/server/lib/reporting/extended-information.ts
index a533abff0b..377ba9408c 100644
--- a/plugins/main/server/lib/reporting/extended-information.ts
+++ b/plugins/main/server/lib/reporting/extended-information.ts
@@ -24,7 +24,7 @@ import { getSettingDefaultValue } from '../../../common/services/settings';
* @param {Array} ids ids of agents
* @param {String} apiId API id
*/
- export async function buildAgentsTable(context, printer: ReportPrinter, agentIDs: string[], apiId: string, groupID: string = '') {
+export async function buildAgentsTable(context, printer: ReportPrinter, agentIDs: string[], apiId: string, groupID: string = '') {
const dateFormat = await context.core.uiSettings.client.get('dateFormat');
if ((!agentIDs || !agentIDs.length) && !groupID) return;
log('reporting:buildAgentsTable', `${agentIDs.length} agents for API ${apiId}`, 'info');
@@ -32,7 +32,7 @@ import { getSettingDefaultValue } from '../../../common/services/settings';
let agentsData = [];
if (groupID) {
let totalAgentsInGroup = null;
- do{
+ do {
const { data: { data: { affected_items, total_affected_items } } } = await context.wazuh.api.client.asCurrentUser.request(
'GET',
`/groups/${groupID}/agents`,
@@ -46,7 +46,7 @@ import { getSettingDefaultValue } from '../../../common/services/settings';
);
!totalAgentsInGroup && (totalAgentsInGroup = total_affected_items);
agentsData = [...agentsData, ...affected_items];
- }while(agentsData.length < totalAgentsInGroup);
+ } while (agentsData.length < totalAgentsInGroup);
} else {
for (const agentID of agentIDs) {
try {
@@ -72,7 +72,7 @@ import { getSettingDefaultValue } from '../../../common/services/settings';
}
}
- if(agentsData.length){
+ if (agentsData.length) {
// Print a table with agent/s information
printer.addSimpleTable({
columns: [
@@ -96,7 +96,7 @@ import { getSettingDefaultValue } from '../../../common/services/settings';
}
}),
});
- }else if(!agentsData.length && groupID){
+ } else if (!agentsData.length && groupID) {
// For group reports when there is no agents in the group
printer.addContent({
text: 'There are no agents in this group.',
@@ -135,12 +135,12 @@ export async function extendedInformation(
filters,
allowedAgentsFilter,
pattern = getSettingDefaultValue('pattern'),
- agent = null
+ agent = null,
) {
try {
log(
'reporting:extendedInformation',
- `Section ${section} and tab ${tab}, API is ${apiId}. From ${from} to ${to}. Filters ${filters}. Index pattern ${pattern}`,
+ `Section ${section} and tab ${tab}, API is ${apiId}. From ${from} to ${to}. Filters ${JSON.stringify(filters)}. Index pattern ${pattern}`,
'info'
);
if (section === 'agents' && !agent) {
@@ -181,7 +181,7 @@ export async function extendedInformation(
return count
? `${count} of ${totalAgents} agents have ${vulnerabilitiesLevel.toLocaleLowerCase()} vulnerabilities.`
: undefined;
- } catch (error) {}
+ } catch (error) { }
})
)
).filter((vulnerabilitiesResponse) => vulnerabilitiesResponse);
diff --git a/plugins/main/server/lib/reporting/gdpr-request.ts b/plugins/main/server/lib/reporting/gdpr-request.ts
index e058804be2..26fa191c99 100644
--- a/plugins/main/server/lib/reporting/gdpr-request.ts
+++ b/plugins/main/server/lib/reporting/gdpr-request.ts
@@ -28,10 +28,6 @@ export const topGDPRRequirements = async (
allowedAgentsFilter,
pattern = getSettingDefaultValue('pattern')
) => {
- if (filters.includes('rule.gdpr: exists')) {
- const [head, tail] = filters.split('AND rule.gdpr: exists');
- filters = head + tail;
- };
try {
const base = {};
@@ -50,12 +46,6 @@ export const topGDPRRequirements = async (
}
});
- base.query.bool.must.push({
- exists: {
- field: 'rule.gdpr'
- }
- });
-
const response = await context.core.opensearch.client.asCurrentUser.search({
index: pattern,
body: base
@@ -86,10 +76,6 @@ export const getRulesByRequirement = async (
requirement,
pattern = getSettingDefaultValue('pattern')
) => {
- if (filters.includes('rule.gdpr: exists')) {
- const [head, tail] = filters.split('AND rule.gdpr: exists');
- filters = head + tail;
- };
try {
const base = {};
@@ -119,8 +105,13 @@ export const getRulesByRequirement = async (
}
});
- base.query.bool.must[0].query_string.query =
- base.query.bool.must[0].query_string.query + ` AND rule.gdpr: "${requirement}"`;
+ base.query.bool.filter.push({
+ match_phrase: {
+ 'rule.gdpr': {
+ query: requirement
+ }
+ }
+ });
const response = await context.core.opensearch.client.asCurrentUser.search({
index: pattern,
diff --git a/plugins/main/server/lib/reporting/pci-request.ts b/plugins/main/server/lib/reporting/pci-request.ts
index 811d615561..65a39755c2 100644
--- a/plugins/main/server/lib/reporting/pci-request.ts
+++ b/plugins/main/server/lib/reporting/pci-request.ts
@@ -28,9 +28,6 @@ export const topPCIRequirements = async (
allowedAgentsFilter,
pattern = getSettingDefaultValue('pattern')
) => {
- if (filters.includes('rule.pci_dss: exists')) {
- filters = filters.replace('AND rule.pci_dss: exists', '');
- };
try {
const base = {};
@@ -49,12 +46,6 @@ export const topPCIRequirements = async (
}
});
- base.query.bool.must.push({
- exists: {
- field: 'rule.pci_dss'
- }
- });
-
const response = await context.core.opensearch.client.asCurrentUser.search({
index: pattern,
body: base
@@ -100,9 +91,6 @@ export const getRulesByRequirement = async (
requirement,
pattern = getSettingDefaultValue('pattern')
) => {
- if (filters.includes('rule.pci_dss: exists')) {
- filters = filters.replace('AND rule.pci_dss: exists', '');
- };
try {
const base = {};
@@ -132,11 +120,13 @@ export const getRulesByRequirement = async (
}
});
- base.query.bool.must[0].query_string.query =
- base.query.bool.must[0].query_string.query +
- ' AND rule.pci_dss: "' +
- requirement +
- '"';
+ base.query.bool.filter.push({
+ match_phrase: {
+ 'rule.pci_dss': {
+ query: requirement
+ }
+ }
+ });
const response = await context.core.opensearch.client.asCurrentUser.search({
index: pattern,
@@ -154,7 +144,7 @@ export const getRulesByRequirement = async (
) {
return accum;
};
- accum.push({ruleID: bucket['3'].buckets[0].key, ruleDescription: bucket.key});
+ accum.push({ ruleID: bucket['3'].buckets[0].key, ruleDescription: bucket.key });
return accum;
}, []);
} catch (error) {
diff --git a/plugins/main/server/lib/reporting/rootcheck-request.ts b/plugins/main/server/lib/reporting/rootcheck-request.ts
index 0eede80de9..8318bbc22a 100644
--- a/plugins/main/server/lib/reporting/rootcheck-request.ts
+++ b/plugins/main/server/lib/reporting/rootcheck-request.ts
@@ -46,9 +46,11 @@ export const top5RootkitsDetected = async (
}
});
- base.query.bool.must[0].query_string.query =
- base.query.bool.must[0].query_string.query +
- ' AND "rootkit" AND "detected"';
+ base.query?.bool?.must?.push({
+ query_string: {
+ query: '"rootkit" AND "detected"'
+ }
+ });
const response = await context.core.opensearch.client.asCurrentUser.search({
index: pattern,
@@ -97,9 +99,11 @@ export const agentsWithHiddenPids = async (
}
});
- base.query.bool.must[0].query_string.query =
- base.query.bool.must[0].query_string.query +
- ' AND "process" AND "hidden"';
+ base.query?.bool?.must?.push({
+ query_string: {
+ query: '"process" AND "hidden"'
+ }
+ });
// "aggregations": { "1": { "value": 1 } }
const response = await context.core.opensearch.client.asCurrentUser.search({
@@ -126,7 +130,7 @@ export const agentsWithHiddenPids = async (
* @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
* @returns {Array}
*/
-export const agentsWithHiddenPorts = async(
+export const agentsWithHiddenPorts = async (
context,
gte,
lte,
@@ -147,8 +151,11 @@ export const agentsWithHiddenPorts = async(
}
});
- base.query.bool.must[0].query_string.query =
- base.query.bool.must[0].query_string.query + ' AND "port" AND "hidden"';
+ base.query?.bool?.must?.push({
+ query_string: {
+ query: '"port" AND "hidden"'
+ }
+ });
// "aggregations": { "1": { "value": 1 } }
const response = await context.core.opensearch.client.asCurrentUser.search({
diff --git a/plugins/main/server/lib/reporting/tsc-request.ts b/plugins/main/server/lib/reporting/tsc-request.ts
index aa59d6f6bc..2d03c804b8 100644
--- a/plugins/main/server/lib/reporting/tsc-request.ts
+++ b/plugins/main/server/lib/reporting/tsc-request.ts
@@ -12,14 +12,14 @@
import { Base } from './base-query';
import { getSettingDefaultValue } from '../../../common/services/settings';
- /**
- * Returns top 5 TSC requirements
- * @param {Number} context Endpoint context
- * @param {Number} gte Timestamp (ms) from
- * @param {Number} lte Timestamp (ms) to
- * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
- * @returns {Array}
- */
+/**
+ * Returns top 5 TSC requirements
+ * @param {Number} context Endpoint context
+ * @param {Number} gte Timestamp (ms) from
+ * @param {Number} lte Timestamp (ms) to
+ * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
+ * @returns {Array}
+ */
export const topTSCRequirements = async (
context,
gte,
@@ -28,9 +28,6 @@ export const topTSCRequirements = async (
allowedAgentsFilter,
pattern = getSettingDefaultValue('pattern')
) => {
- if (filters.includes('rule.tsc: exists')) {
- filters = filters.replace('AND rule.tsc: exists', '');
- };
try {
const base = {};
@@ -49,12 +46,6 @@ export const topTSCRequirements = async (
}
});
- base.query.bool.must.push({
- exists: {
- field: 'rule.tsc'
- }
- });
-
const response = await context.core.opensearch.client.asCurrentUser.search({
index: pattern,
body: base
@@ -100,9 +91,6 @@ export const getRulesByRequirement = async (
requirement,
pattern = getSettingDefaultValue('pattern')
) => {
- if (filters.includes('rule.tsc: exists')) {
- filters = filters.replace('AND rule.tsc: exists', '');
- };
try {
const base = {};
@@ -132,11 +120,13 @@ export const getRulesByRequirement = async (
}
});
- base.query.bool.must[0].query_string.query =
- base.query.bool.must[0].query_string.query +
- ' AND rule.tsc: "' +
- requirement +
- '"';
+ base.query.bool.filter.push({
+ match_phrase: {
+ 'rule.tsc': {
+ query: requirement
+ }
+ }
+ });
const response = await context.core.opensearch.client.asCurrentUser.search({
index: pattern,
@@ -155,7 +145,7 @@ export const getRulesByRequirement = async (
) {
return accum;
};
- accum.push({ruleID: bucket['3'].buckets[0].key, ruleDescription: bucket.key});
+ accum.push({ ruleID: bucket['3'].buckets[0].key, ruleDescription: bucket.key });
return accum;
}, []);
} catch (error) {
diff --git a/plugins/main/server/routes/wazuh-reporting.test.ts b/plugins/main/server/routes/wazuh-reporting.test.ts
index 034377cbeb..45a9482c24 100644
--- a/plugins/main/server/routes/wazuh-reporting.test.ts
+++ b/plugins/main/server/routes/wazuh-reporting.test.ts
@@ -10,20 +10,23 @@ import { WazuhReportingRoutes } from './wazuh-reporting';
import { WazuhUtilsCtrl } from '../controllers/wazuh-utils/wazuh-utils';
import md5 from 'md5';
import path from 'path';
-import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../lib/filesystem';
+import {
+ createDataDirectoryIfNotExists,
+ createDirectoryIfNotExists,
+} from '../lib/filesystem';
import {
WAZUH_DATA_CONFIG_APP_PATH,
WAZUH_DATA_CONFIG_DIRECTORY_PATH,
WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH,
WAZUH_DATA_LOGS_DIRECTORY_PATH,
WAZUH_DATA_ABSOLUTE_PATH,
- WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH
+ WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH,
} from '../../common/constants';
import { execSync } from 'child_process';
import fs from 'fs';
jest.mock('../lib/reporting/extended-information', () => ({
- extendedInformation: jest.fn()
+ extendedInformation: jest.fn(),
}));
const USER_NAME = 'admin';
const loggingService = loggingSystemMock.create();
@@ -31,18 +34,19 @@ const logger = loggingService.get();
const context = {
wazuh: {
security: {
- getCurrentUser: (request) => {
+ getCurrentUser: request => {
// x-test-username header doesn't exist when the platform or plugin are running.
// It is used to generate the output of this method so we can simulate the user
// that does the request to the endpoint and is expected by the endpoint handlers
// of the plugin.
const username = request.headers['x-test-username'];
- return { username, hashUsername: md5(username) }
- }
- }
- }
+ return { username, hashUsername: md5(username) };
+ },
+ },
+ },
};
-const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, context);
+const enhanceWithContext = (fn: (...args: any[]) => any) =>
+ fn.bind(null, context);
let server, innerServer;
// BEFORE ALL
@@ -71,12 +75,24 @@ beforeAll(async () => {
} as any;
server = new HttpServer(loggingService, 'tests');
const router = new Router('', logger, enhanceWithContext);
- const { registerRouter, server: innerServerTest, ...rest } = await server.setup(config);
+ const {
+ registerRouter,
+ server: innerServerTest,
+ ...rest
+ } = await server.setup(config);
innerServer = innerServerTest;
// Mock decorator
- jest.spyOn(WazuhUtilsCtrl.prototype as any, 'routeDecoratorProtectedAdministratorRoleValidToken')
- .mockImplementation((handler) => async (...args) => handler(...args));
+ jest
+ .spyOn(
+ WazuhUtilsCtrl.prototype as any,
+ 'routeDecoratorProtectedAdministratorRoleValidToken',
+ )
+ .mockImplementation(
+ handler =>
+ async (...args) =>
+ handler(...args),
+ );
// Register routes
WazuhUtilsRoutes(router);
@@ -124,11 +140,21 @@ describe('[endpoint] GET /reports', () => {
// Create directories and file/s within directory.
directories.forEach(({ username, files }) => {
const hashUsername = md5(username);
- createDirectoryIfNotExists(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, hashUsername));
+ createDirectoryIfNotExists(
+ path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, hashUsername),
+ );
if (files) {
Array.from(Array(files).keys()).forEach(indexFile => {
- console.log('Generating', username, indexFile)
- fs.closeSync(fs.openSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, hashUsername, `report_${indexFile}.pdf`), 'w'));
+ fs.closeSync(
+ fs.openSync(
+ path.join(
+ WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH,
+ hashUsername,
+ `report_${indexFile}.pdf`,
+ ),
+ 'w',
+ ),
+ );
});
}
});
@@ -139,13 +165,16 @@ describe('[endpoint] GET /reports', () => {
execSync(`rm -rf ${WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH}`);
});
- it.each(directories)('get reports of $username. status response: $responseStatus', async ({ username, files }) => {
- const response = await supertest(innerServer.listener)
- .get(`/reports`)
- .set('x-test-username', username)
- .expect(200);
- expect(response.body.reports).toHaveLength(files);
- });
+ it.each(directories)(
+ 'get reports of $username. status response: $responseStatus',
+ async ({ username, files }) => {
+ const response = await supertest(innerServer.listener)
+ .get(`/reports`)
+ .set('x-test-username', username)
+ .expect(200);
+ expect(response.body.reports).toHaveLength(files);
+ },
+ );
});
describe('[endpoint] PUT /utils/configuration', () => {
@@ -174,16 +203,33 @@ describe('[endpoint] PUT /utils/configuration', () => {
// expectedMD5 variable is a verified md5 of a report generated with this header and footer
// If any of the parameters is changed this variable should be updated with the new md5
it.each`
- footer | header | responseStatusCode | expectedMD5 | tab
- ${null} | ${null} | ${200} | ${'7b6fa0e2a5911880d17168800c173f89'} | ${'pm'}
- ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'}| ${200} | ${'51b268066bb5107e5eb0a9d791a89d0c'} | ${'general'}
- ${''} | ${''} | ${200} | ${'23d5e0eedce38dc6df9e98e898628f68'} | ${'fim'}
- ${'Custom Footer'} | ${null} | ${200} | ${'2b16be2ea88d3891cda7acb6075826d9'} | ${'aws'}
- ${null} | ${'Custom Header'} | ${200} | ${'91e30564f157942718afdd97db3b4ddf'} | ${'gcp'}
-`(`Set custom report header and footer - Verify PDF output`, async ({footer, header, responseStatusCode, expectedMD5, tab}) => {
-
+ footer | header | responseStatusCode | expectedMD5 | tab
+ ${null} | ${null} | ${200} | ${'a261be6b2e5fb18bb7434ee46a01e174'} | ${'pm'}
+ ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'} | ${200} | ${'51b268066bb5107e5eb0a9d791a89d0c'} | ${'general'}
+ ${''} | ${''} | ${200} | ${'8e8fbd90e08b810f700fcafbfdcdf638'} | ${'fim'}
+ ${'Custom Footer'} | ${null} | ${200} | ${'2b16be2ea88d3891cda7acb6075826d9'} | ${'aws'}
+ ${null} | ${'Custom Header'} | ${200} | ${'4a55136aaf8b5f6b544a03fe46917552'} | ${'gcp'}
+ `(
+ `Set custom report header and footer - Verify PDF output`,
+ async ({ footer, header, responseStatusCode, expectedMD5, tab }) => {
// Mock PDF report parameters
- const reportBody = { "array": [], "filters": [], "time": { "from": '2022-10-01T09:59:40.825Z', "to": '2022-10-04T09:59:40.825Z' }, "searchBar": "", "tables": [], "tab": tab, "section": "overview", "agents": false, "browserTimezone": "Europe/Madrid", "indexPatternTitle": "wazuh-alerts-*", "apiId": "default" };
+ const reportBody = {
+ array: [],
+ serverSideQuery: [],
+ filters: [],
+ time: {
+ from: '2022-10-01T09:59:40.825Z',
+ to: '2022-10-04T09:59:40.825Z',
+ },
+ searchBar: '',
+ tables: [],
+ tab: tab,
+ section: 'overview',
+ agents: false,
+ browserTimezone: 'Europe/Madrid',
+ indexPatternTitle: 'wazuh-alerts-*',
+ apiId: 'default',
+ };
// Define custom configuration
const configurationBody = {};
@@ -203,10 +249,18 @@ describe('[endpoint] PUT /utils/configuration', () => {
.expect(responseStatusCode);
if (typeof footer == 'string') {
- expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.footer']).toMatch(configurationBody['customization.reports.footer']);
+ expect(
+ responseConfig.body?.data?.updatedConfiguration?.[
+ 'customization.reports.footer'
+ ],
+ ).toMatch(configurationBody['customization.reports.footer']);
}
if (typeof header == 'string') {
- expect(responseConfig.body?.data?.updatedConfiguration?.['customization.reports.header']).toMatch(configurationBody['customization.reports.header']);
+ expect(
+ responseConfig.body?.data?.updatedConfiguration?.[
+ 'customization.reports.header'
+ ],
+ ).toMatch(configurationBody['customization.reports.header']);
}
}
@@ -216,16 +270,19 @@ describe('[endpoint] PUT /utils/configuration', () => {
.set('x-test-username', USER_NAME)
.send(reportBody)
.expect(200);
- const fileName = responseReport.body?.message.match(/([A-Z-0-9]*\.pdf)/gi)[0];
+ const fileName =
+ responseReport.body?.message.match(/([A-Z-0-9]*\.pdf)/gi)[0];
const userPath = md5(USER_NAME);
const reportPath = `${WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH}/${userPath}/${fileName}`;
const PDFbuffer = fs.readFileSync(reportPath);
const PDFcontent = PDFbuffer.toString('utf8');
- const content = PDFcontent
- .replace(/\[<[a-z0-9].+> <[a-z0-9].+>\]/gi, '')
- .replace(/(obj\n\(D:[0-9].+Z\)\nendobj)/gi, '');
+ const content = PDFcontent.replace(
+ /\[<[a-z0-9].+> <[a-z0-9].+>\]/gi,
+ '',
+ ).replace(/(obj\n\(D:[0-9].+Z\)\nendobj)/gi, '');
const PDFmd5 = md5(content);
expect(PDFmd5).toBe(expectedMD5);
- });
+ },
+ );
});
diff --git a/plugins/main/server/routes/wazuh-reporting.ts b/plugins/main/server/routes/wazuh-reporting.ts
index 946e73ac5d..7f78a27458 100644
--- a/plugins/main/server/routes/wazuh-reporting.ts
+++ b/plugins/main/server/routes/wazuh-reporting.ts
@@ -55,30 +55,31 @@ export function WazuhReportingRoutes(router: IRouter) {
]);
router.post({
- path: '/reports/modules/{moduleID}',
- validate: {
- body: schema.object({
- array: schema.any(),
- browserTimezone: schema.string(),
- filters: schema.maybe(schema.any()),
- agents: schema.maybe(schema.oneOf([agentIDValidation, schema.boolean()])),
- components: schema.maybe(schema.any()),
- searchBar: schema.maybe(schema.string()),
- section: schema.maybe(schema.string()),
- tab: schema.string(),
- tables: schema.maybe(schema.any()),
- time: schema.oneOf([schema.object({
- from: schema.string(),
- to: schema.string()
- }), schema.string()]),
- indexPatternTitle: schema.string(),
- apiId: schema.string()
- }),
- params: schema.object({
- moduleID: moduleIDValidation
- })
- }
- },
+ path: '/reports/modules/{moduleID}',
+ validate: {
+ body: schema.object({
+ array: schema.any(),
+ browserTimezone: schema.string(),
+ serverSideQuery: schema.maybe(schema.any()),
+ filters: schema.maybe(schema.any()),
+ agents: schema.maybe(schema.oneOf([agentIDValidation, schema.boolean()])),
+ components: schema.maybe(schema.any()),
+ searchBar: schema.maybe(schema.string()),
+ section: schema.maybe(schema.string()),
+ tab: schema.string(),
+ tables: schema.maybe(schema.any()),
+ time: schema.oneOf([schema.object({
+ from: schema.string(),
+ to: schema.string()
+ }), schema.string()]),
+ indexPatternTitle: schema.string(),
+ apiId: schema.string()
+ }),
+ params: schema.object({
+ moduleID: moduleIDValidation
+ })
+ }
+ },
(context, request, response) => ctrl.createReportsModules(context, request, response)
);
@@ -124,6 +125,7 @@ export function WazuhReportingRoutes(router: IRouter) {
body: schema.object({
array: schema.any(),
browserTimezone: schema.string(),
+ serverSideQuery: schema.maybe(schema.any()),
filters: schema.maybe(schema.any()),
agents: schema.maybe(schema.oneOf([schema.string(), schema.boolean()])),
components: schema.maybe(schema.any()),
@@ -148,33 +150,33 @@ export function WazuhReportingRoutes(router: IRouter) {
// Fetch specific report
router.get({
- path: '/reports/{name}',
- validate: {
- params: schema.object({
- name: ReportFilenameValidation
- })
- }
- },
+ path: '/reports/{name}',
+ validate: {
+ params: schema.object({
+ name: ReportFilenameValidation
+ })
+ }
+ },
(context, request, response) => ctrl.getReportByName(context, request, response)
);
// Delete specific report
router.delete({
- path: '/reports/{name}',
- validate: {
- params: schema.object({
- name: ReportFilenameValidation
- })
- }
- },
+ path: '/reports/{name}',
+ validate: {
+ params: schema.object({
+ name: ReportFilenameValidation
+ })
+ }
+ },
(context, request, response) => ctrl.deleteReportByName(context, request, response)
)
// Fetch the reports list
router.get({
- path: '/reports',
- validate: false
- },
+ path: '/reports',
+ validate: false
+ },
(context, request, response) => ctrl.getReports(context, request, response)
);
}