diff --git a/CHANGELOG.md b/CHANGELOG.md index d68424a6da..b31ba95654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,27 @@ All notable changes to the Wazuh app project will be documented in this file. -## Wazuh v4.9.0 - OpenSearch Dashboards 2.11.0 - Revision 00 +## Wazuh v4.9.0 - OpenSearch Dashboards 2.12.0 - Revision 00 ### Added - Support for Wazuh 4.9.0 - Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145) +- Added a migration task to setup the configuration using a configuration file [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337) +- Added the ability to manage the API hosts from the Server APIs [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337) +- Added edit groups action to Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250) +- Added global actions add agents to groups and remove agents from groups to Endpoints Summary [#6274](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6274) +- Added propagation of updates from the table to dashboard visualizations in Endpoints summary [#6460](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6460) ### Changed -- Removed embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) [#6235](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6235) [#6254](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6254) [#6285](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6285) [#6288](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6288) [#6290](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6290) [#6289](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6289) [#6286](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6286) [#6275](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6275) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6297](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6297) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6291](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) +- Removed embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) [#6235](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6235) [#6254](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6254) [#6285](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6285) [#6288](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6288) [#6290](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6290) [#6289](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6289) [#6286](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6286) [#6275](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6275) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6297](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6297) [#6287](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6291](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6287) [#6459](https://github.com/wazuh/wazuh-dashboard-plugins/pull/#6459) - Develop logic of a new index for the fim module [#6227](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6227) - Allow editing groups for an agent from Endpoints Summary [#6250](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6250) +- Changed as the configuration is defined and stored [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337) +- Change the view of API is down and check connection to Server APIs application [#6337](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6337) +- Changed the usage of the endpoint GET /groups/{group_id}/files/{file_name} [#6385](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6385) +- Refactoring and redesign endpoints summary visualizations [#6268](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6268) ### Fixed @@ -31,7 +40,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 4.8.1 -## Wazuh v4.8.0 - OpenSearch Dashboards 2.10.0 - Revision 03 +## Wazuh v4.8.0 - OpenSearch Dashboards 2.10.0 - Revision 05 ### Added @@ -39,21 +48,21 @@ All notable changes to the Wazuh app project will be documented in this file. - Added the ability to check if there are available updates from the UI. [#6093](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6093) [#6256](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6256) [#6328](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6328) - Added remember server address check [#5791](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5791) - Added the ssl_agent_ca configuration to the SSL Settings form [#6083](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6083) -- Added global vulnerabilities dashboards [#5896](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5896) [#6179](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6179) [#6173](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6173) [#6147](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6147) [#6231](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6231) [#6246](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6246) [#6321](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6321) [#6338](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6338) [#6356](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6356) +- Added global vulnerabilities dashboards [#5896](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5896) [#6179](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6179) [#6173](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6173) [#6147](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6147) [#6231](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6231) [#6246](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6246) [#6321](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6321) [#6338](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6338) [#6356](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6356) [#6396](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6396) [#6399](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6399) [#6405](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6405) [#6410](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6410) [#6424](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6424) [#6422](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6422) [#6429](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6429) [#6448](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6448) - Added an agent selector to the IT Hygiene application [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) - Added query results limit when the search exceed 10000 hits [#6106](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6106) - Added a redirection button to Endpoint Summary from IT Hygiene application [#6176](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6176) -- Added information icon with tooltip on the most active agent in the endpoint summary view [#6364](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6364) +- Added information icon with tooltip on the most active agent in the endpoint summary view [#6364](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6364) [#6421](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6421) - Added a dash with a tooltip in the server APIs table when the run as is disabled [#6354](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6354) ### Changed -- Moved the plugin menu to platform applications into the side menu [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) [#6226](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6226) [#6244](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6244) +- Moved the plugin menu to platform applications into the side menu [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) [#6226](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6226) [#6244](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6244) [#6176](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6176) [#6423](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6423) - Changed dashboards. [#6035](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6035) - Change the display order of tabs in all modules. [#6067](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6067) -- Upgraded the `axios` dependency to `1.6.1` [#5062](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5062) +- Upgraded the `axios` dependency to `1.6.1` [#6114](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6114) - Changed the api configuration title in the Server APIs section. [#6373](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6373) -- Changed overview home top KPIs. [#6379](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6379) +- Changed overview home top KPIs. [#6379](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6379) [#6408](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6408) ### Fixed @@ -73,17 +82,27 @@ All notable changes to the Wazuh app project will be documented in this file. - Fixed implicit filter close button in the search bar [#6346](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6346) - Fixed the help menu, to be consistent and avoid duplication [#6374](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6374) - Fixed the axis label visual bug from dashboards [#6378](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6378) +- Fixed a error pop-up spawn in MITRE ATT&CK [#6431](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6431) ### Removed - Removed the `disabled_roles` and `customization.logo.sidebar` settings [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) - Removed the ability to configure the visibility of modules and removed `extensions.*` settings [#5840](https://github.com/wazuh/wazuh-dashboard-plugins/pull/5840) -- Removed the application menu in the IT Hygiene application [#6176](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6176) - Removed the implicit filter of WQL language of the search bar UI [#6174](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6174) - Removed notice of old Discover deprecation [#6341](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6341) - Removed compilation date field from the app [#6366](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6366) - Removed WAZUH_REGISTRATION_SERVER from Windows agent deployment command [#6361](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6361) +## Wazuh v4.7.3 - OpenSearch Dashboards 2.8.0 - Revision 02 + +### Added + +- Support for Wazuh 4.7.3 + +### Fixed + +- Fixed CDB List import file feature [#6458](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6458) + ## Wazuh v4.7.2 - OpenSearch Dashboards 2.8.0 - Revision 02 ### Added diff --git a/docker/imposter/agents/agent_distinct.js b/docker/imposter/agents/agent_distinct.js index 158a6e5c2d..f2b237bc07 100644 --- a/docker/imposter/agents/agent_distinct.js +++ b/docker/imposter/agents/agent_distinct.js @@ -1,56 +1,323 @@ if (!String.prototype.includes) { - String.prototype.includes = function(search, start) { - 'use strict'; - - if (search instanceof RegExp) { - throw TypeError('first argument must not be a RegExp'); - } - if (start === undefined) { start = 0; } - return this.indexOf(search, start) !== -1; - }; -} + String.prototype.includes = function (search, start) { + 'use strict'; -function generateResponse(items, field, search){ - return { - data: { - affected_items: items.filter(function(item){ - return search ? item.includes(search) : true; - }).map(function(item){ - var obj = {}; - obj[field] = item - return obj; - }), - total_affected_items: 5, - total_failed_items: 0, - failed_items: [] - }, - message: "All selected agents information was returned", - error: 0 + if (search instanceof RegExp) { + throw TypeError('first argument must not be a RegExp'); + } + if (start === undefined) { + start = 0; + } + return this.indexOf(search, start) !== -1; }; +} + +var fields = context.request.queryParams.fields; + +/* Based on agents.json */ +var originalResponse = { + data: { + affected_items: [ + { + os: { + arch: 'x86_64', + major: '2', + name: 'Amazon Linux', + platform: 'amzn', + uname: + 'Linux |wazuh-manager-master-0 |4.14.114-105.126.amzn2.x86_64 |#1 SMP Tue May 7 02:26:40 UTC 2019 |x86_64', + version: '2', + }, + group: [ + 'default', + 'test', + 'test2', + 'test3', + 'test4', + 'test5', + 'test6', + 'test7', + 'test8', + 'test9', + 'test10', + ], + ip: 'FE80:0034:0223:A000:0002:B3FF:0000:8329', + id: '000', + registerIP: 'FE80:0034:0223:A000:0002:B3FF:0000:8329', + dateAdd: '2022-08-25T16:17:46Z', + name: 'wazuh-manager-master-0', + status: 'active', + manager: 'wazuh-manager-master-0', + node_name: 'master', + lastKeepAlive: '9999-12-31T23:59:59Z', + version: 'Wazuh v4.4.0', + group_config_status: 'synced', + status_code: 0, + count: 1, + }, + { + os: { + arch: 'x86_64', + major: '2', + name: 'Amazon Linux', + platform: 'amzn', + uname: + 'Linux |wazuh-manager-master-0 |4.14.114-105.126.amzn2.x86_64 |#1 SMP Tue May 7 02:26:40 UTC 2019 |x86_64', + version: '2', + }, + group: ['default', 'test', 'test2', 'test3', 'test4', 'test5'], + ip: 'FE80:1234:2223:A000:2202:B3FF:FE1E:8329', + id: '001', + registerIP: 'FE80:1234:2223:A000:2202:B3FF:FE1E:8329', + dateAdd: '2022-08-25T16:17:46Z', + name: 'wazuh-manager-master-0', + status: 'active', + manager: 'wazuh-manager-master-0', + node_name: 'master', + lastKeepAlive: '9999-12-31T23:59:59Z', + version: 'Wazuh v4.4.0', + group_config_status: 'not synced', + status_code: 0, + count: 1, + }, + { + os: { + arch: 'x86_64', + major: '2', + name: 'Amazon Linux', + platform: 'amzn', + uname: + 'Linux |wazuh-manager-master-0 |4.14.114-105.126.amzn2.x86_64 |#1 SMP Tue May 7 02:26:40 UTC 2019 |x86_64', + version: '2', + }, + group: ['default', 'test', 'test2'], + ip: '127.0.0.1', + id: '002', + registerIP: '127.0.0.1', + dateAdd: '2022-08-25T16:17:46Z', + name: 'wazuh-manager-master-0', + status: 'active', + manager: 'wazuh-manager-master-0', + node_name: 'master', + lastKeepAlive: '9999-12-31T23:59:59Z', + version: 'Wazuh v4.5.0', + group_config_status: 'synced', + status_code: 0, + count: 1, + }, + { + os: { + build: '19045', + major: '10', + minor: '0', + name: 'Microsoft Windows 10 Home Single Language', + platform: 'windows', + uname: 'Microsoft Windows 10 Home Single Language', + version: '10.0.19045', + }, + disconnection_time: '2023-03-14T04:37:42Z', + manager: 'test.com', + status: 'disconnected', + name: 'disconnected-agent', + dateAdd: '1970-01-01T00:00:00Z', + group: ['default', 'test'], + lastKeepAlive: '2023-03-14T04:20:51Z', + node_name: 'node01', + registerIP: 'any', + id: '003', + version: 'Wazuh v4.3.10', + ip: '111.111.1.111', + mergedSum: 'e669d89eba52f6897060fc65a45300ac', + configSum: '97fccbb67e250b7c80aadc8d0dc59abe', + group_config_status: 'not synced', + status_code: 1, + count: 1, + }, + { + status: 'never_connected', + name: 'never_connected_agent', + dateAdd: '2023-03-14T09:44:11Z', + node_name: 'unknown', + registerIP: 'any', + id: '004', + ip: 'any', + group_config_status: 'not synced', + status_code: 4, + count: 1, + }, + { + os: { + arch: 'x86_64', + major: '2', + name: 'macOS High Sierra', + platform: 'darwin', + uname: + 'macOS High Sierra |wazuh-manager-master-0 |4.14.114-105.126.amzn2.x86_64 |#1 SMP Tue May 7 02:26:40 UTC 2019 |x86_64', + version: '2', + }, + ip: '127.0.0.1', + id: '005', + group: ['default'], + registerIP: '127.0.0.1', + dateAdd: '2022-08-25T16:17:46Z', + name: 'macOS High Sierra agent', + status: 'disconnected', + manager: 'wazuh-manager-master-0', + node_name: 'master', + lastKeepAlive: '9999-12-31T23:59:59Z', + version: 'Wazuh v4.5.0', + group_config_status: 'synced', + status_code: 2, + count: 1, + }, + { + os: { + name: 'Ubuntu', + platform: 'ubuntu', + uname: + 'Linux |f288f4c59dbc |5.19.0-35-generic |#36~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Feb 17 15:17:25 UTC 2 |x86_64', + version: '18.04.6 LTS', + }, + group_config_status: 'not synced', + status_code: 0, + ip: '172.19.0.27', + status: 'pending', + name: 'Pending agent', + group: ['default'], + node_name: 'master-node', + version: 'Wazuh v4.4.0', + lastKeepAlive: '2023-03-16T15:15:05+00:00', + id: '006', + dateAdd: '2023-03-16T15:14:47+00:00', + count: 1, + }, + { + status: 'never_connected', + name: 'never_connected_agent-2', + dateAdd: '2023-03-14T09:44:11Z', + node_name: 'unknown', + registerIP: 'any', + id: '007', + ip: 'any', + group_config_status: 'not synced', + status_code: 5, + count: 1, + }, + { + status: 'never_connected', + name: 'never_connected_agent-3', + dateAdd: '2023-03-14T09:44:11Z', + node_name: 'unknown', + registerIP: 'any', + id: '008', + ip: 'any', + group_config_status: 'not synced', + status_code: 1, + count: 1, + }, + { + status: 'never_connected', + name: 'never_connected_agent-4', + dateAdd: '2023-03-14T09:44:11Z', + node_name: 'unknown', + registerIP: 'any', + id: '009', + ip: 'any', + group_config_status: 'not synced', + status_code: 2, + count: 1, + }, + { + os: { + build: '19045', + major: '10', + minor: '0', + name: 'Microsoft Windows 10 Home Single Language', + platform: 'windows', + uname: 'Microsoft Windows 10 Home Single Language', + version: '10.0.19045', + }, + disconnection_time: '2023-03-14T04:37:42Z', + manager: 'test.com', + status: 'disconnected', + name: 'disconnected-agent-2', + dateAdd: '1970-01-01T00:00:00Z', + group: ['default', 'test'], + lastKeepAlive: '2023-03-14T04:20:51Z', + node_name: 'node01', + registerIP: 'any', + id: '010', + version: 'Wazuh v4.3.10', + ip: '111.111.1.111', + mergedSum: 'e669d89eba52f6897060fc65a45300ac', + configSum: '97fccbb67e250b7c80aadc8d0dc59abe', + group_config_status: 'not synced', + count: 1, + }, + ], + total_affected_items: 5, + total_failed_items: 0, + failed_items: [], + }, + message: 'All selected agents information was returned', + error: 0, }; -var mock = { - 'configSum': ['97fccbb67e250b7c80aadc8d0dc59abc', '97fccbb67e250b7c80aadc8d0dc59abd', '97fccbb67e250b7c80aadc8d0dc59abf', '97fccbb67e250b7c80aadc8d0dc59abe'], - 'dateAdd': ['2022-08-25T16:17:46Z', '2022-08-25T17:17:46Z', '2022-08-25T18:17:46Z'], - 'id': ['001', '002','003','004','005'], - 'ip': ['127.0.0.1', '127.0.0.2','127.0.0.3','127.0.0.4','127.0.0.5'], - 'group': ['default', 'windows', 'linux', 'rhel', 'arch'], - 'group_config_status': ['not synced', 'synced'], - 'lastKeepAlive': ['2022-08-25T16:17:46Z', '2022-08-25T17:17:46Z', '2022-08-25T18:17:46Z'], - 'manager': ['test.com', 'test2.com'], - 'mergedSum': ['e669d89eba52f6897060fc65a45300ac', 'e669d89eba52f6897060fc65a45300ad', 'e669d89eba52f6897060fc65a45300ae', 'e669d89eba52f6897060fc65a45300af'], - 'name': ['linux-agent', 'windows-agent'], - 'node_name': ['node01', 'node02', 'node03'], - 'os.platform': ['ubuntu', 'windows', 'darwin', 'amzn'], - 'status': ['active', 'disconnected', 'pending', 'never_connected'], - 'version': ['4.3.10', '4.4.0'] +var selectedFields = fields.split(','); + +var combinationsCount = {}; + +originalResponse.data.affected_items.forEach(function (agent) { + var combinationKey = selectedFields + .map(function (field) { + if (field.includes('.')) { + var subfields = field.split('.'); + return agent[subfields[0]] !== undefined + ? agent[subfields[0]][subfields[1]] + : 'unknown'; + } + return agent[field]; + }) + .join(','); + + if (!combinationsCount[combinationKey]) { + combinationsCount[combinationKey] = { count: 0 }; + selectedFields.forEach(function (field) { + if (field.includes('.')) { + var subfields = field.split('.'); + if (!combinationsCount[combinationKey][subfields[0]]) { + combinationsCount[combinationKey][subfields[0]] = {}; + } + combinationsCount[combinationKey][subfields[0]][subfields[1]] = + agent[subfields[0]] && agent[subfields[0]][subfields[1]] + ? agent[subfields[0]][subfields[1]] + : 'unknown'; + } else { + combinationsCount[combinationKey][field] = agent[field]; + } + }); + } + combinationsCount[combinationKey].count += agent.count; +}); + +var transformedResponse = { + data: { + affected_items: [], + total_affected_items: 0, + total_failed_items: 0, + failed_items: [], + }, + message: 'All selected agents information was returned', + error: 0, }; -var field = context.request.queryParams.fields; -var search = context.request.queryParams.search; +for (var key in combinationsCount) { + if (combinationsCount.hasOwnProperty(key)) { + transformedResponse.data.affected_items.push(combinationsCount[key]); + } +} -var responseJSON = generateResponse(mock[field], field, search); +transformedResponse.data.total_affected_items = + transformedResponse.data.affected_items.length; -respond() - .withStatusCode(200) - .withData(JSON.stringify(responseJSON)) \ No newline at end of file +respond().withStatusCode(200).withData(JSON.stringify(transformedResponse)); diff --git a/docker/imposter/agents/group_files.js b/docker/imposter/agents/group_files.js new file mode 100644 index 0000000000..dd4dd0e3de --- /dev/null +++ b/docker/imposter/agents/group_files.js @@ -0,0 +1,10 @@ +var raw_param = context.request.queryParams; + +switch (raw_param.raw) { + case 'true': + respond().withStatusCode(200).withFile('agents/group_files_raw.xml'); + break; + default: + respond().withStatusCode(200).withFile('agents/group_files_default.json'); + break; +} diff --git a/docker/imposter/agents/group_files_default.json b/docker/imposter/agents/group_files_default.json new file mode 100644 index 0000000000..4a07fe87d9 --- /dev/null +++ b/docker/imposter/agents/group_files_default.json @@ -0,0 +1,27 @@ +{ + "data": { + "vars": "None", + "controls": [ + { + "name": "CIS - Testing against the CIS Debian Linux Benchmark v1.", + "cis": [], + "pci": [], + "condition": "all required", + "reference": "CIS_Debian_Benchmark_v1.0pdf", + "checks": [ + "f:/etc/debian_version;", + "f:/proc/sys/kernel/ostype -> Linux;" + ] + }, + { + "name": "CIS - Debian Linux - 1.4 - Robust partition scheme - /tmp is not on its own partition", + "cis": [], + "pci": [], + "condition": "any", + "reference": "https://benchmarks.cisecurity.org/tools2/linux/CIS_Debian_Benchmark_v1.0.pdf", + "checks": ["f:/etc/fstab -> !r:/tmp;"] + } + ] + }, + "error": 0 +} diff --git a/docker/imposter/agents/group_files_raw.xml b/docker/imposter/agents/group_files_raw.xml new file mode 100644 index 0000000000..5f12e3c920 --- /dev/null +++ b/docker/imposter/agents/group_files_raw.xml @@ -0,0 +1,3 @@ + + + diff --git a/docker/imposter/manager/configuration.js b/docker/imposter/manager/configuration.js index 02984110f2..d85926ec49 100644 --- a/docker/imposter/manager/configuration.js +++ b/docker/imposter/manager/configuration.js @@ -17,7 +17,7 @@ switch (pathConfiguration[0]) { case 'wmodules': respond() .withStatusCode(200) - .withFile('manager/configuration/monitor_reports.json'); + .withFile('manager/configuration/wmodules_wmodules.json'); break; default: diff --git a/docker/imposter/security/security-actions.json b/docker/imposter/security/security-actions.json index 88ba661fa8..ded985ae2b 100644 --- a/docker/imposter/security/security-actions.json +++ b/docker/imposter/security/security-actions.json @@ -142,8 +142,8 @@ "GET /groups/{group_id}/agents", "GET /groups/{group_id}/configuration", "GET /groups/{group_id}/files", - "GET /groups/{group_id}/files/{file_name}/json", - "GET /groups/{group_id}/files/{file_name}/xml", + "GET /groups/{group_id}/files/{file_name}", + "GET /groups/{group_id}/files/{file_name}?raw=true", "GET /overview/agents" ] }, diff --git a/docker/imposter/wazuh-config.yml b/docker/imposter/wazuh-config.yml index 7b6a91548d..dd53ec3723 100755 --- a/docker/imposter/wazuh-config.yml +++ b/docker/imposter/wazuh-config.yml @@ -394,11 +394,10 @@ resources: # Get a file in group - method: GET - path: /groups/{group_id}/files/{file_name}/json - - # Get a file in group - - method: GET - path: /groups/{group_id}/files/{file_name}/xml + path: /groups/{group_id}/files/{file_name} + response: + statusCode: 200 + scriptFile: agents/group_files.js # ===================================================== # # LISTS diff --git a/docker/osd-dev/dev.sh b/docker/osd-dev/dev.sh index 33ae08a32f..9a03af5bee 100755 --- a/docker/osd-dev/dev.sh +++ b/docker/osd-dev/dev.sh @@ -13,6 +13,8 @@ os_versions=( '2.9.0' '2.10.0' '2.11.0' + '2.11.1' + '2.12.0' ) osd_versions=( @@ -28,8 +30,8 @@ osd_versions=( '2.9.0' '2.10.0' '2.11.0' - '4.6.0' - '4.7.0' + '2.11.1' + '2.12.0' ) wzs_version=( diff --git a/docker/osd-dev/dev.yml b/docker/osd-dev/dev.yml index efdf6553bd..c13e118fe0 100755 --- a/docker/osd-dev/dev.yml +++ b/docker/osd-dev/dev.yml @@ -204,8 +204,8 @@ services: mkdir -p /etc/filebeat echo admin | filebeat keystore add username --stdin --force echo ${PASSWORD}| filebeat keystore add password --stdin --force - curl -so /etc/filebeat/wazuh-template.json https://raw.githubusercontent.com/wazuh/wazuh/4.3/extensions/elasticsearch/7.x/wazuh-template.json - curl -s https://packages.wazuh.com/4.x/filebeat/wazuh-filebeat-0.4.tar.gz | tar -xvz -C /usr/share/filebeat/module + curl -so /etc/filebeat/wazuh-template.json https://raw.githubusercontent.com/wazuh/wazuh/v4.7.2/extensions/elasticsearch/7.x/wazuh-template.json + curl -s https://packages.wazuh.com/4.x/filebeat/wazuh-filebeat-0.3.tar.gz | tar -xvz -C /usr/share/filebeat/module # copy filebeat to preserve correct permissions without # affecting host filesystem cp /tmp/filebeat.yml /usr/share/filebeat/filebeat.yml diff --git a/plugins/main/common/api-info/endpoints.json b/plugins/main/common/api-info/endpoints.json index ac58e3d7d8..bbf674c1f1 100644 --- a/plugins/main/common/api-info/endpoints.json +++ b/plugins/main/common/api-info/endpoints.json @@ -7,9 +7,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.default_controller.default_info", "description": "Return basic information about the API", "summary": "Get API info", - "tags": [ - "API Info" - ], + "tags": ["API Info"], "query": [ { "name": "pretty", @@ -26,9 +24,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agents", "description": "Return information about all available agents or a list of them", "summary": "List agents", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -65,10 +61,7 @@ "description": "Agent groups configuration sync status", "schema": { "type": "string", - "enum": [ - "synced", - "not synced" - ] + "enum": ["synced", "not synced"] } }, { @@ -213,12 +206,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "active", - "pending", - "never_connected", - "disconnected" - ] + "enum": ["active", "pending", "never_connected", "disconnected"] }, "minItems": 1 } @@ -246,9 +234,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_config", "description": "Return the active configuration the agent is currently using. This can be different from the configuration present in the configuration file, if it has been modified and the agent has not been restarted yet", "summary": "Get active configuration", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -346,9 +332,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_daemon_stats", "description": "Return Wazuh statistical information from specified daemons in a specified agent", "summary": "Get Wazuh daemon stats from an agent", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -370,10 +354,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "wazuh-analysisd", - "wazuh-remoted" - ] + "enum": ["wazuh-analysisd", "wazuh-remoted"] } } }, @@ -400,9 +381,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_sync_agent", "description": "Return whether the agent configuration has been synchronized with the agent or not. This can be useful to check after updating a group configuration", "summary": "Get configuration sync status", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -440,9 +419,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_key", "description": "Return the key of an agent", "summary": "Get key", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -480,9 +457,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_component_stats", "description": "Return Wazuh's {component} statistical information from agent {agent_id}", "summary": "Get agent's component stats", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -501,10 +476,7 @@ "required": true, "schema": { "type": "string", - "enum": [ - "logcollector", - "agent" - ] + "enum": ["logcollector", "agent"] } } ], @@ -532,9 +504,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_no_group", "description": "Return a list with all the available agents without an assigned group", "summary": "List agents without group", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "limit", @@ -614,9 +584,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_outdated", "description": "Return the list of outdated agents", "summary": "List outdated agents", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "limit", @@ -685,9 +653,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_fields", "description": "Return all the different combinations that agents have for the selected fields. It also indicates the total number of agents that have each combination", "summary": "List agents distinct", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "fields", @@ -767,9 +733,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_summary_os", "description": "Return a summary of the OS of available agents", "summary": "Summarize agents OS", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "pretty", @@ -794,9 +758,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_summary_status", "description": "Return a summary of the connection and groups configuration synchronization statuses of available agents", "summary": "Summarize agents status", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "pretty", @@ -821,9 +783,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agent_upgrade", "description": "Return the agents upgrade results", "summary": "Get upgrade results", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -949,9 +909,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.ciscat_controller.get_agents_ciscat_results", "description": "Return the agent's ciscat results info", "summary": "Get results", - "tags": [ - "Ciscat" - ], + "tags": ["Ciscat"], "args": [ { "name": ":agent_id", @@ -1113,9 +1071,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_configuration_node", "description": "Return wazuh configuration used in node {node_id}. The 'section' and 'field' parameters will be ignored if 'raw' parameter is provided.", "summary": "Get node config", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1207,9 +1163,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_node_config", "description": "Return the requested configuration in JSON format for the specified node", "summary": "Get node active configuration", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":component", @@ -1305,9 +1259,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_daemon_stats_node", "description": "Return Wazuh statistical information from specified daemons in a specified cluster node", "summary": "Get Wazuh daemon stats from a cluster node", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1327,11 +1279,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "wazuh-analysisd", - "wazuh-remoted", - "wazuh-db" - ] + "enum": ["wazuh-analysisd", "wazuh-remoted", "wazuh-db"] } } }, @@ -1358,9 +1306,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_info_node", "description": "Return basic information about a specified node such as version, compilation date, installation path", "summary": "Get node info", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1396,9 +1342,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_log_node", "description": "Return the last 2000 wazuh log entries in the specified node", "summary": "Get node logs", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1520,9 +1464,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_log_summary_node", "description": "Return a summary of the last 2000 wazuh log entries in the specified node", "summary": "Get node logs summary", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1558,9 +1500,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_stats_node", "description": "Return Wazuh statistical information in node {node_id} for the current or specified date", "summary": "Get node stats", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1604,9 +1544,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_stats_analysisd_node", "description": "Return Wazuh analysisd statistical information in node {node_id}", "summary": "Get node stats analysisd", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1642,9 +1580,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_stats_hourly_node", "description": "Return Wazuh statistical information in node {node_id} per hour. Each number in the averages field represents the average of alerts per hour", "summary": "Get node stats hour", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1680,9 +1616,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_stats_remoted_node", "description": "Return Wazuh remoted statistical information in node {node_id}", "summary": "Get node stats remoted", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1718,9 +1652,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_stats_weekly_node", "description": "Return Wazuh statistical information in node {node_id} per week. Each number in the averages field represents the average of alerts per hour for that specific day", "summary": "Get node stats week", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1756,9 +1688,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_status_node", "description": "Return the status of all Wazuh daemons in node node_id", "summary": "Get node status", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -1794,9 +1724,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_api_config", "description": "Return the API configuration of all nodes (or a list of them) in JSON format", "summary": "Get nodes API config", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "nodes_list", @@ -1831,9 +1759,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_conf_validation", "description": "Return whether the Wazuh configuration is correct or not in all cluster nodes or a list of them", "summary": "Check nodes config", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "nodes_list", @@ -1868,9 +1794,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_healthcheck", "description": "Return cluster healthcheck information for all nodes or a list of them. Such information includes last keep alive, last synchronization time and number of agents reporting on each node", "summary": "Get nodes healthcheck", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "nodes_list", @@ -1905,9 +1829,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_config", "description": "Return the current node cluster configuration", "summary": "Get local node config", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "pretty", @@ -1932,9 +1854,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_cluster_node", "description": "Return basic information about the cluster node receiving the request", "summary": "Get local node info", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "pretty", @@ -1959,9 +1879,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_cluster_nodes", "description": "Get information about all nodes in the cluster or a list of them", "summary": "Get nodes info", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "distinct", @@ -2049,10 +1967,7 @@ "description": "Filter by node type", "schema": { "type": "string", - "enum": [ - "worker", - "master" - ] + "enum": ["worker", "master"] } }, { @@ -2070,9 +1985,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_nodes_ruleset_sync_status", "description": "Return ruleset synchronization status for all nodes or a list of them. This synchronization only covers the user custom ruleset", "summary": "Get cluster nodes ruleset synchronization status", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "nodes_list", @@ -2107,9 +2020,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.get_status", "description": "Return information about the cluster status", "summary": "Get cluster status", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "pretty", @@ -2134,9 +2045,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.decoder_controller.get_decoders", "description": "Return information about all decoders included in ossec.conf. This information include decoder's route, decoder's name, decoder's file among others", "summary": "List decoders", - "tags": [ - "Decoders" - ], + "tags": ["Decoders"], "query": [ { "name": "decoder_names", @@ -2244,11 +2153,7 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": [ - "enabled", - "disabled", - "all" - ], + "enum": ["enabled", "disabled", "all"], "minItems": 1 } }, @@ -2267,9 +2172,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.decoder_controller.get_decoders_files", "description": "Return information about all decoders files used in Wazuh. This information include decoder's file, decoder's route and decoder's status among others", "summary": "Get files", - "tags": [ - "Decoders" - ], + "tags": ["Decoders"], "query": [ { "name": "distinct", @@ -2366,11 +2269,7 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": [ - "enabled", - "disabled", - "all" - ], + "enum": ["enabled", "disabled", "all"], "minItems": 1 } }, @@ -2389,9 +2288,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.decoder_controller.get_file", "description": "Get the content of a specified decoder file", "summary": "Get decoders file content", - "tags": [ - "Decoders" - ], + "tags": ["Decoders"], "args": [ { "name": ":filename", @@ -2443,9 +2340,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.decoder_controller.get_decoders_parents", "description": "Return information about all parent decoders. A parent decoder is a decoder used as base of other decoders", "summary": "Get parent decoders", - "tags": [ - "Decoders" - ], + "tags": ["Decoders"], "query": [ { "name": "limit", @@ -2518,9 +2413,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_cis_cat_results", "description": "Return CIS-CAT results for all agents or a list of them", "summary": "Get agents CIS-CAT results", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -2675,9 +2568,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_hardware_info", "description": "Return all agents (or a list of them) hardware info. This information include cpu, ram, scan info among others of all agents", "summary": "Get agents hardware", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -2814,9 +2705,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_hotfixes_info", "description": "Return all agents (or a list of them) hotfixes info", "summary": "Get agents hotfixes", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -2909,9 +2798,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_network_address_info", "description": "Return all agents (or a list of them) IPv4 and IPv6 addresses associated to their network interfaces. This information include used IP protocol, interface, and IP address among others", "summary": "Get agents netaddr", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "address", @@ -3029,9 +2916,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_network_interface_info", "description": "Return all agents (or a list of them) network interfaces. This information includes rx, scan, tx info and some network information among other", "summary": "Get agents netiface", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "adapter", @@ -3230,9 +3115,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_network_protocol_info", "description": "Return all agents (or a list of them) routing configuration for each network interface. This information includes interface, type protocol information among other", "summary": "Get agents netproto", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -3253,12 +3136,7 @@ "schema": { "type": "string", "description": "DHCP status", - "enum": [ - "enabled", - "disabled", - "unknown", - "BOOTP" - ] + "enum": ["enabled", "disabled", "unknown", "BOOTP"] } }, { @@ -3356,9 +3234,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_os_info", "description": "Return all agents (or a list of them) OS info. This information includes os information, architecture information among other", "summary": "Get agents OS", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -3484,9 +3360,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_packages_info", "description": "Return all agents (or a list of them) packages info. This information includes name, section, size, and priority information of all packages among other", "summary": "Get agents packages", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -3610,9 +3484,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_ports_info", "description": "Return all agents (or a list of them) ports info. This information includes local IP, Remote IP, protocol information among other", "summary": "Get agents ports", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -3762,9 +3634,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.get_processes_info", "description": "Return all agents (or a list of them) processes info", "summary": "Get agents processes", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -3962,9 +3832,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_list_group", "description": "Get information about all groups or a list of them. Returns a list containing basic information about each group such as number of agents belonging to the group and the checksums of the configuration and shared files", "summary": "Get groups", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "query": [ { "name": "distinct", @@ -4085,9 +3953,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_agents_in_group", "description": "Return the list of agents that belong to the specified group", "summary": "Get agents in a group", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "args": [ { "name": ":group_id", @@ -4179,12 +4045,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "active", - "pending", - "never_connected", - "disconnected" - ] + "enum": ["active", "pending", "never_connected", "disconnected"] }, "minItems": 1 } @@ -4204,9 +4065,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_group_config", "description": "Return the group configuration defined in the `agent.conf` file", "summary": "Get group configuration", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "args": [ { "name": ":group_id", @@ -4264,9 +4123,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_group_files", "description": "Return the files placed under the group directory", "summary": "Get group files", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "args": [ { "name": ":group_id", @@ -4383,13 +4240,11 @@ ] }, { - "name": "/groups/:group_id/files/:file_name/json", + "name": "/groups/:group_id/files/:file_name", "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_group_file_json", "description": "Return the content of the specified group file parsed to JSON", "summary": "Get a file in group", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "args": [ { "name": ":file_name", @@ -4427,12 +4282,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "conf", - "rootkit_files", - "rootkit_trojans", - "rcl" - ] + "enum": ["conf", "rootkit_files", "rootkit_trojans", "rcl"] } } }, @@ -4447,13 +4297,11 @@ ] }, { - "name": "/groups/:group_id/files/:file_name/xml", + "name": "/groups/:group_id/files/:file_name?raw=true", "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.get_group_file_xml", "description": "Return the contents of the specified group file parsed to XML", "summary": "Get a file in group", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "args": [ { "name": ":file_name", @@ -4491,12 +4339,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "conf", - "rootkit_files", - "rootkit_trojans", - "rcl" - ] + "enum": ["conf", "rootkit_files", "rootkit_trojans", "rcl"] } } }, @@ -4515,9 +4358,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cdb_list_controller.get_lists", "description": "Return the contents of all CDB lists. Optionally, the result can be filtered by several criteria. See available parameters for more details", "summary": "Get CDB lists info", - "tags": [ - "Lists" - ], + "tags": ["Lists"], "query": [ { "name": "distinct", @@ -4624,9 +4465,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cdb_list_controller.get_lists_files", "description": "Return the path from all CDB lists. Use this method to know all the CDB lists and their location in the filesystem relative to Wazuh installation folder", "summary": "Get CDB lists files", - "tags": [ - "Lists" - ], + "tags": ["Lists"], "query": [ { "name": "filename", @@ -4707,9 +4546,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cdb_list_controller.get_file", "description": "Return the content of a CDB list file. Only the filename can be specified. It will be searched recursively if not found", "summary": "Get CDB list file content", - "tags": [ - "Lists" - ], + "tags": ["Lists"], "args": [ { "name": ":filename", @@ -4753,9 +4590,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_api_config", "description": "Return the local API configuration in JSON format", "summary": "Get API config", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -4780,9 +4615,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_configuration", "description": "Return wazuh configuration used. The 'section' and 'field' parameters will be ignored if 'raw' parameter is provided.", "summary": "Get configuration", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "distinct", @@ -4871,9 +4704,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_manager_config_ondemand", "description": "Return the requested active configuration in JSON format", "summary": "Get active configuration", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "args": [ { "name": ":component", @@ -4960,9 +4791,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_conf_validation", "description": "Return whether the Wazuh configuration is correct", "summary": "Check config", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -4987,9 +4816,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_daemon_stats", "description": "Return Wazuh statistical information from specified daemons", "summary": "Get Wazuh daemon stats", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "daemons_list", @@ -4998,11 +4825,7 @@ "type": "array", "items": { "type": "string", - "enum": [ - "wazuh-analysisd", - "wazuh-remoted", - "wazuh-db" - ] + "enum": ["wazuh-analysisd", "wazuh-remoted", "wazuh-db"] } } }, @@ -5029,9 +4852,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_info", "description": "Return basic information such as version, compilation date, installation path", "summary": "Get information", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -5056,9 +4877,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_log", "description": "Return the last 2000 wazuh log entries", "summary": "Get logs", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "distinct", @@ -5169,9 +4988,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_log_summary", "description": "Return a summary of the last 2000 wazuh log entries", "summary": "Get logs summary", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -5196,9 +5013,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_stats", "description": "Return Wazuh statistical information for the current or specified date", "summary": "Get stats", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "date", @@ -5231,9 +5046,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_stats_analysisd", "description": "Return Wazuh analysisd statistical information", "summary": "Get stats analysisd", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -5258,9 +5071,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_stats_hourly", "description": "Return Wazuh statistical information per hour. Each number in the averages field represents the average of alerts per hour", "summary": "Get stats hour", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -5285,9 +5096,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_stats_remoted", "description": "Return Wazuh remoted statistical information", "summary": "Get stats remoted", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -5312,9 +5121,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_stats_weekly", "description": "Return Wazuh statistical information per week. Each number in the averages field represents the average of alerts per hour for that specific day", "summary": "Get stats week", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -5339,9 +5146,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.get_status", "description": "Return the status of all Wazuh daemons", "summary": "Get status", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -5366,9 +5171,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.check_available_version", "description": "Return if there is any available update", "summary": "Check available updates", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "force_query", @@ -5393,9 +5196,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.mitre_controller.get_groups", "description": "Return the groups from MITRE database", "summary": "Get MITRE groups", - "tags": [ - "MITRE" - ], + "tags": ["MITRE"], "query": [ { "name": "distinct", @@ -5494,9 +5295,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.mitre_controller.get_metadata", "description": "Return the metadata from MITRE database", "summary": "Get MITRE metadata", - "tags": [ - "MITRE" - ], + "tags": ["MITRE"], "query": [ { "name": "pretty", @@ -5521,9 +5320,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.mitre_controller.get_mitigations", "description": "Return the mitigations from MITRE database", "summary": "Get MITRE mitigations", - "tags": [ - "MITRE" - ], + "tags": ["MITRE"], "query": [ { "name": "distinct", @@ -5622,9 +5419,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.mitre_controller.get_references", "description": "Return the references from MITRE database", "summary": "Get MITRE references", - "tags": [ - "MITRE" - ], + "tags": ["MITRE"], "query": [ { "name": "limit", @@ -5715,9 +5510,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.mitre_controller.get_software", "description": "Return the software from MITRE database", "summary": "Get MITRE software", - "tags": [ - "MITRE" - ], + "tags": ["MITRE"], "query": [ { "name": "distinct", @@ -5816,9 +5609,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.mitre_controller.get_tactics", "description": "Return the tactics from MITRE database", "summary": "Get MITRE tactics", - "tags": [ - "MITRE" - ], + "tags": ["MITRE"], "query": [ { "name": "distinct", @@ -5917,9 +5708,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.mitre_controller.get_techniques", "description": "Return the techniques from MITRE database", "summary": "Get MITRE techniques", - "tags": [ - "MITRE" - ], + "tags": ["MITRE"], "query": [ { "name": "distinct", @@ -6018,9 +5807,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.overview_controller.get_overview_agents", "description": "Return a dictionary with a full agents overview", "summary": "Get agents overview", - "tags": [ - "Overview" - ], + "tags": ["Overview"], "query": [ { "name": "pretty", @@ -6045,9 +5832,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rootcheck_controller.get_rootcheck_agent", "description": "Return the rootcheck database of an agent", "summary": "Get results", - "tags": [ - "Rootcheck" - ], + "tags": ["Rootcheck"], "args": [ { "name": ":agent_id", @@ -6172,9 +5957,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rootcheck_controller.get_last_scan_agent", "description": "Return the timestamp of the last rootcheck scan of an agent", "summary": "Get last scan datetime", - "tags": [ - "Rootcheck" - ], + "tags": ["Rootcheck"], "args": [ { "name": ":agent_id", @@ -6212,9 +5995,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rule_controller.get_rules", "description": "Return a list containing information about each rule such as file where it's defined, description, rule group, status, etc", "summary": "List rules", - "tags": [ - "Rules" - ], + "tags": ["Rules"], "query": [ { "name": "distinct", @@ -6387,11 +6168,7 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": [ - "enabled", - "disabled", - "all" - ], + "enum": ["enabled", "disabled", "all"], "minItems": 1 } }, @@ -6418,9 +6195,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rule_controller.get_rules_files", "description": "Return a list containing all files used to define rules and their status", "summary": "Get files", - "tags": [ - "Rules" - ], + "tags": ["Rules"], "query": [ { "name": "distinct", @@ -6517,11 +6292,7 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": [ - "enabled", - "disabled", - "all" - ], + "enum": ["enabled", "disabled", "all"], "minItems": 1 } }, @@ -6540,9 +6311,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rule_controller.get_file", "description": "Get the content of a specified rule in the ruleset", "summary": "Get rules file content", - "tags": [ - "Rules" - ], + "tags": ["Rules"], "args": [ { "name": ":filename", @@ -6594,9 +6363,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rule_controller.get_rules_groups", "description": "Return a list containing all rule groups names", "summary": "Get groups", - "tags": [ - "Rules" - ], + "tags": ["Rules"], "query": [ { "name": "limit", @@ -6658,9 +6425,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rule_controller.get_rules_requirement", "description": "Return all specified requirement names defined in the Wazuh ruleset", "summary": "Get requirements", - "tags": [ - "Rules" - ], + "tags": ["Rules"], "args": [ { "name": ":requirement", @@ -6740,9 +6505,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.sca_controller.get_sca_agent", "description": "Return the security SCA database of an agent", "summary": "Get results", - "tags": [ - "SCA" - ], + "tags": ["SCA"], "args": [ { "name": ":agent_id", @@ -6865,9 +6628,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.sca_controller.get_sca_checks", "description": "Return the policy monitoring alerts for a given policy", "summary": "Get policy checks", - "tags": [ - "SCA" - ], + "tags": ["SCA"], "args": [ { "name": ":agent_id", @@ -7078,9 +6839,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_rbac_actions", "description": "Get all RBAC actions, including the potential related resources and endpoints.", "summary": "List RBAC actions", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "endpoint", @@ -7104,9 +6863,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_security_config", "description": "Return the security configuration in JSON format", "summary": "Get security config", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -7131,9 +6888,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_policies", "description": "Get all policies in the system, including the administrator policy", "summary": "List policies", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "distinct", @@ -7233,9 +6988,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_rbac_resources", "description": "This method should be called to get all current defined RBAC resources.", "summary": "List RBAC resources", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -7272,9 +7025,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_roles", "description": "For a specific list, indicate the ids separated by commas. Example: ?role_ids=1,2,3", "summary": "List roles", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "distinct", @@ -7374,9 +7125,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_rules", "description": "Get a list of security rules from the system or all of them. These rules must be mapped with roles to obtain certain access privileges. For a specific list, indicate the ids separated by commas. Example: ?rule_ids=1,2,3", "summary": "List security rules", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "distinct", @@ -7476,9 +7225,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.deprecated_login_user", "description": "This method should be called to get an API token. This token will expire after auth_token_exp_timeout seconds (default: 900). This value can be changed using PUT /security/config", "summary": "Login", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "raw", @@ -7495,9 +7242,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_users", "description": "Get the information of a specified user", "summary": "List users", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "distinct", @@ -7597,9 +7342,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_user_me", "description": "Get the information of the current user", "summary": "Get current user info", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -7624,9 +7367,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.get_user_me_policies", "description": "Get the processed policies information for the current user", "summary": "Get current user processed policies", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -7643,9 +7384,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscheck_controller.get_syscheck_agent", "description": "Return FIM findings in the specified agent", "summary": "Get results", - "tags": [ - "Syscheck" - ], + "tags": ["Syscheck"], "args": [ { "name": ":agent_id", @@ -7665,10 +7404,7 @@ "description": "Filter by architecture", "schema": { "type": "string", - "enum": [ - "[x32]", - "[x64]" - ] + "enum": ["[x32]", "[x64]"] } }, { @@ -7795,11 +7531,7 @@ "description": "Filter by file type. Registry_key and registry_value types are only available in Windows agents", "schema": { "type": "string", - "enum": [ - "file", - "registry_key", - "registry_value" - ] + "enum": ["file", "registry_key", "registry_value"] } }, { @@ -7833,9 +7565,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscheck_controller.get_last_scan_agent", "description": "Return when the last syscheck scan started and ended. If the scan is still in progress the end date will be unknown", "summary": "Get last scan datetime", - "tags": [ - "Syscheck" - ], + "tags": ["Syscheck"], "args": [ { "name": ":agent_id", @@ -7873,9 +7603,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_hardware_info", "description": "Return the agent's hardware info. This information include cpu, ram, scan info among others", "summary": "Get agent hardware", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -7924,9 +7652,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_hotfix_info", "description": "Return all hotfixes installed by Microsoft(R) in Windows(R) systems (KB... fixes)", "summary": "Get agent hotfixes", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -8034,9 +7760,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_network_address_info", "description": "Return the agent's network address info. This information include used IP protocol, interface, IP address among others", "summary": "Get agent netaddr", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -8177,9 +7901,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_network_interface_info", "description": "Return the agent's network interface info. This information include rx, scan, tx info and some network information among others", "summary": "Get agent netiface", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -8392,9 +8114,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_network_protocol_info", "description": "Return the agent's routing configuration for each network interface", "summary": "Get agent netproto", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -8415,12 +8135,7 @@ "schema": { "type": "string", "description": "DHCP status", - "enum": [ - "enabled", - "disabled", - "unknown", - "BOOTP" - ] + "enum": ["enabled", "disabled", "unknown", "BOOTP"] } }, { @@ -8533,9 +8248,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_os_info", "description": "Return the agent's OS info. This information include os information, architecture information among others of all agents", "summary": "Get agent OS", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -8584,9 +8297,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_packages_info", "description": "Return the agent's packages info. This information include name, section, size, priority information of all packages among others", "summary": "Get agent packages", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -8725,9 +8436,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_ports_info", "description": "Return the agent's ports info. This information include local IP, Remote IP, protocol information among others", "summary": "Get agent ports", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -8892,9 +8601,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscollector_controller.get_processes_info", "description": "Return the agent's processes info", "summary": "Get agent processes", - "tags": [ - "Syscollector" - ], + "tags": ["Syscollector"], "args": [ { "name": ":agent_id", @@ -9107,9 +8814,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.task_controller.get_tasks_status", "description": "Returns all available information about the specified tasks", "summary": "List tasks", - "tags": [ - "Tasks" - ], + "tags": ["Tasks"], "query": [ { "name": "agents_list", @@ -9251,9 +8956,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.active_response_controller.run_command", "description": "Run an Active Response command on all agents or a list of them", "summary": "Run command", - "tags": [ - "Active-response" - ], + "tags": ["Active-response"], "query": [ { "name": "agents_list", @@ -9311,9 +9014,7 @@ } } }, - "required": [ - "command" - ] + "required": ["command"] } ] }, @@ -9322,9 +9023,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.put_agent_single_group", "description": "Assign an agent to a specified group", "summary": "Assign agent to group", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -9379,9 +9078,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.restart_agent", "description": "Restart the specified agent", "summary": "Restart agent", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -9419,9 +9116,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.put_multiple_agent_single_group", "description": "Assign all agents or a list of them to the specified group", "summary": "Assign agents to group", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -9476,9 +9171,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.restart_agents_by_group", "description": "Restart all agents which belong to a given group", "summary": "Restart agents in group", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":group_id", @@ -9515,9 +9208,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.restart_agents_by_node", "description": "Restart all agents which belong to a specific given node", "summary": "Restart agents in node", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":node_id", @@ -9553,9 +9244,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.reconnect_agents", "description": "Force reconnect all agents or a list of them", "summary": "Force reconnect agents", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -9593,9 +9282,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.restart_agents", "description": "Restart all agents or a list of them", "summary": "Restart agents", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -9633,9 +9320,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.put_upgrade_agents", "description": "Upgrade agents using a WPK file from online repository. When upgrading more than 3000 agents at the same time, it's highly recommended to use the parameter `wait_for_complete` set to `true` to avoid a possible API timeout", "summary": "Upgrade agents", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -9794,9 +9479,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.put_upgrade_custom_agents", "description": "Upgrade the agents using a local WPK file. When upgrading more than 3000 agents at the same time, it's highly recommended to use the parameter `wait_for_complete` set to `true` to avoid a possible API timeout", "summary": "Upgrade agents custom", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -9940,9 +9623,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.update_configuration", "description": "Replace wazuh configuration for the given node with the data contained in the API request", "summary": "Update node configuration", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "args": [ { "name": ":node_id", @@ -9978,9 +9659,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cluster_controller.put_restart", "description": "Restart all nodes in the cluster or a list of them", "summary": "Restart nodes", - "tags": [ - "Cluster" - ], + "tags": ["Cluster"], "query": [ { "name": "nodes_list", @@ -10015,9 +9694,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.decoder_controller.put_file", "description": "Upload or replace a user decoder file content", "summary": "Update decoders file", - "tags": [ - "Decoders" - ], + "tags": ["Decoders"], "args": [ { "name": ":filename", @@ -10069,9 +9746,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.put_group_config", "description": "Update an specified group's configuration. This API call expects a full valid XML file with the shared configuration tags/syntax", "summary": "Update group configuration", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "args": [ { "name": ":group_id", @@ -10108,9 +9783,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cdb_list_controller.put_file", "description": "Replace or upload a CDB list file with the data contained in the API request", "summary": "Update CDB list file", - "tags": [ - "Lists" - ], + "tags": ["Lists"], "args": [ { "name": ":filename", @@ -10154,9 +9827,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.logtest_controller.run_logtest_tool", "description": "Run logtest tool to check if a specified log raises any alert among other information", "summary": "Run logtest", - "tags": [ - "Logtest" - ], + "tags": ["Logtest"], "query": [ { "name": "pretty", @@ -10178,11 +9849,7 @@ "body": [ { "type": "object", - "required": [ - "event", - "log_format", - "location" - ], + "required": ["event", "log_format", "location"], "properties": { "token": { "type": "string", @@ -10209,9 +9876,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.update_configuration", "description": "Replace Wazuh configuration with the data contained in the API request", "summary": "Update Wazuh configuration", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -10236,9 +9901,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.manager_controller.put_restart", "description": "Restart the wazuh manager", "summary": "Restart manager", - "tags": [ - "Manager" - ], + "tags": ["Manager"], "query": [ { "name": "pretty", @@ -10263,9 +9926,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rootcheck_controller.put_rootcheck", "description": "Run rootcheck scan in all agents or a list of them", "summary": "Run scan", - "tags": [ - "Rootcheck" - ], + "tags": ["Rootcheck"], "query": [ { "name": "agents_list", @@ -10303,9 +9964,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rule_controller.put_file", "description": "Upload or replace a user ruleset file content", "summary": "Update rules file", - "tags": [ - "Rules" - ], + "tags": ["Rules"], "args": [ { "name": ":filename", @@ -10357,9 +10016,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.put_security_config", "description": "Update the security configuration with the data contained in the API request", "summary": "Update security config", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -10393,10 +10050,7 @@ "rbac_mode": { "description": "RBAC mode (white/black)", "type": "string", - "enum": [ - "white", - "black" - ], + "enum": ["white", "black"], "example": "white" } } @@ -10408,9 +10062,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.update_policy", "description": "Modify a policy, at least one property must be indicated", "summary": "Update policy", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":policy_id", @@ -10474,11 +10126,7 @@ "description": "Effect of the policy" } }, - "required": [ - "actions", - "resources", - "effect" - ] + "required": ["actions", "resources", "effect"] } } } @@ -10489,9 +10137,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.update_role", "description": "Modify a role, cannot modify associated policies in this endpoint, at least one property must be indicated", "summary": "Update role", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":role_id", @@ -10541,9 +10187,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.update_rule", "description": "Modify a security rule by specifying its ID", "summary": "Update security rule", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":rule_id", @@ -10597,18 +10241,14 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.revoke_all_tokens", "description": "This method should be called to revoke all active JWT tokens", "summary": "Revoke JWT tokens", - "tags": [ - "Security" - ] + "tags": ["Security"] }, { "name": "/security/users/:user_id", "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.update_user", "description": "Modify a user's password by specifying their ID", "summary": "Update users", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":user_id", @@ -10656,9 +10296,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.edit_run_as", "description": "Modify a user's allow_run_as flag by specifying their ID", "summary": "Enable/Disable run_as", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":user_id", @@ -10703,9 +10341,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscheck_controller.put_syscheck", "description": "Run FIM scan in all agents", "summary": "Run scan", - "tags": [ - "Syscheck" - ], + "tags": ["Syscheck"], "query": [ { "name": "agents_list", @@ -10748,9 +10384,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.add_agent", "description": "Add a new agent", "summary": "Add agent", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "pretty", @@ -10784,9 +10418,7 @@ "format": "alphanumeric" } }, - "required": [ - "name" - ] + "required": ["name"] } ] }, @@ -10795,9 +10427,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.insert_agent", "description": "Add an agent specifying its name, ID and IP. If an agent with the same name, the same ID or the same IP already exists, replace it using the `force` parameter", "summary": "Add agent full", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "pretty", @@ -10877,9 +10507,7 @@ } } }, - "required": [ - "name" - ] + "required": ["name"] } ] }, @@ -10888,9 +10516,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.post_new_agent", "description": "Add a new agent with name `agent_name`. This agent will use `any` as IP", "summary": "Add agent quick", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agent_name", @@ -10925,9 +10551,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.event_controller.forward_event", "description": "Send security events to analysisd.\n\nThe endpoint is limited to receiving a max of 30 requests per minute and a max bulk size of 100 events per request.", "summary": "Ingest events", - "tags": [ - "Events" - ], + "tags": ["Events"], "query": [ { "name": "pretty", @@ -10958,9 +10582,7 @@ } } }, - "required": [ - "events" - ] + "required": ["events"] } ] }, @@ -10969,9 +10591,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.post_group", "description": "Create a new group", "summary": "Create a group", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "query": [ { "name": "pretty", @@ -11001,9 +10621,7 @@ "maxLength": 128 } }, - "required": [ - "group_id" - ] + "required": ["group_id"] } ] }, @@ -11012,9 +10630,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.add_policy", "description": "Add a new policy, all fields need to be specified", "summary": "Add policy", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -11036,10 +10652,7 @@ "body": [ { "type": "object", - "required": [ - "name", - "policy" - ], + "required": ["name", "policy"], "properties": { "name": { "description": "Policy name", @@ -11070,11 +10683,7 @@ "description": "Effect of the policy" } }, - "required": [ - "actions", - "resources", - "effect" - ] + "required": ["actions", "resources", "effect"] } } } @@ -11085,9 +10694,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.add_role", "description": "Add a new role, all fields need to be specified", "summary": "Add role", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -11109,9 +10716,7 @@ "body": [ { "type": "object", - "required": [ - "name" - ], + "required": ["name"], "properties": { "name": { "type": "string", @@ -11128,9 +10733,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.set_role_policy", "description": "Create a specified relation role-policy, one role may have multiples policies", "summary": "Add policies to role", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":role_id", @@ -11189,9 +10792,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.set_role_rule", "description": "Create a specific role-rule relation. One role may have multiple security rules", "summary": "Add security rules to role", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":role_id", @@ -11241,9 +10842,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.add_rule", "description": "Add a new security rule", "summary": "Add security rule", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -11265,10 +10864,7 @@ "body": [ { "type": "object", - "required": [ - "name", - "rule" - ], + "required": ["name", "rule"], "properties": { "name": { "type": "string", @@ -11289,9 +10885,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.login_user", "description": "This method should be called to get an API token. This token will expire after auth_token_exp_timeout seconds (default: 900). This value can be changed using PUT /security/config", "summary": "Login", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "raw", @@ -11308,9 +10902,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.run_as_login", "description": "This method should be called to get an API token using an authorization context body. This token will expire after auth_token_exp_timeout seconds (default: 900). This value can be changed using PUT /security/config", "summary": "Login auth_context", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "raw", @@ -11327,9 +10919,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.create_user", "description": "Add a new API user to the system", "summary": "Add user", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -11363,10 +10953,7 @@ "format": "password" } }, - "required": [ - "username", - "password" - ] + "required": ["username", "password"] } ] }, @@ -11375,9 +10962,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.set_user_role", "description": "Create a specified relation role-policy, one user may have multiples roles", "summary": "Add roles to user", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":user_id", @@ -11441,9 +11026,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.delete_agents", "description": "Delete all agents or a list of them based on optional criteria", "summary": "Delete agents", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -11606,9 +11189,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.delete_single_agent_multiple_groups", "description": "Remove the agent from all groups or a list of them. The agent will automatically revert to the default group if it is removed from all its assigned groups", "summary": "Remove agent from groups", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -11658,9 +11239,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.delete_single_agent_single_group", "description": "Remove an agent from a specified group. If the agent belongs to several groups, only the specified group will be deleted.", "summary": "Remove agent from group", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "args": [ { "name": ":agent_id", @@ -11708,9 +11287,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.delete_multiple_agent_single_group", "description": "Remove all agents assignment or a list of them from the specified group", "summary": "Remove agents from group", - "tags": [ - "Agents" - ], + "tags": ["Agents"], "query": [ { "name": "agents_list", @@ -11759,9 +11336,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.decoder_controller.delete_file", "description": "Delete a specified decoder file", "summary": "Delete decoders file", - "tags": [ - "Decoders" - ], + "tags": ["Decoders"], "args": [ { "name": ":filename", @@ -11805,9 +11380,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.clear_rootcheck_database", "description": "Clear rootcheck database for all agents or a list of them", "summary": "Clear rootcheck results", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -11846,9 +11419,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.experimental_controller.clear_syscheck_database", "description": "Clear the syscheck database for all agents or a list of them", "summary": "Clear agents FIM results", - "tags": [ - "Experimental" - ], + "tags": ["Experimental"], "query": [ { "name": "agents_list", @@ -11887,9 +11458,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.agent_controller.delete_groups", "description": "Delete all groups or a list of them", "summary": "Delete groups", - "tags": [ - "Groups" - ], + "tags": ["Groups"], "query": [ { "name": "groups_list", @@ -11928,9 +11497,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.cdb_list_controller.delete_file", "description": "Delete a specified CDB list file. Only the filename can be specified. It will be searched recursively if not found", "summary": "Delete CDB list file", - "tags": [ - "Lists" - ], + "tags": ["Lists"], "args": [ { "name": ":filename", @@ -11966,9 +11533,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.logtest_controller.end_logtest_session", "description": "Delete the saved logtest session corresponding to {token}", "summary": "End session", - "tags": [ - "Logtest" - ], + "tags": ["Logtest"], "args": [ { "name": ":token", @@ -12004,9 +11569,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rootcheck_controller.delete_rootcheck", "description": "Clear an agent's rootcheck database", "summary": "Clear results", - "tags": [ - "Rootcheck" - ], + "tags": ["Rootcheck"], "args": [ { "name": ":agent_id", @@ -12044,9 +11607,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.rule_controller.delete_file", "description": "Delete a specified rule file", "summary": "Delete rules file", - "tags": [ - "Rules" - ], + "tags": ["Rules"], "args": [ { "name": ":filename", @@ -12090,9 +11651,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.delete_security_config", "description": "Replaces the security configuration with the original one", "summary": "Restore default security config", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -12117,9 +11676,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.remove_policies", "description": "Delete a list of policies or all policies in the system, roles linked to policies are not going to be removed", "summary": "Delete policies", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "policy_ids", @@ -12157,9 +11714,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.remove_roles", "description": "Policies linked to roles are not going to be removed", "summary": "Delete roles", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -12197,9 +11752,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.remove_role_policy", "description": "Delete a specified relation role-policy", "summary": "Remove policies from role", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":role_id", @@ -12249,9 +11802,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.remove_role_rule", "description": "Delete a specific role-rule relation", "summary": "Remove security rules from role", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":role_id", @@ -12301,9 +11852,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.remove_rules", "description": "Delete a list of security rules or all security rules in the system, roles linked to rules are not going to be deleted", "summary": "Delete security rules", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -12341,18 +11890,14 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.logout_user", "description": "This method should be called to invalidate all the current user's tokens", "summary": "Logout current user", - "tags": [ - "Security" - ] + "tags": ["Security"] }, { "name": "/security/users", "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.delete_users", "description": "Delete a list of users by specifying their IDs", "summary": "Delete users", - "tags": [ - "Security" - ], + "tags": ["Security"], "query": [ { "name": "pretty", @@ -12390,9 +11935,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.security_controller.remove_user_role", "description": "Delete a specified relation user-roles", "summary": "Remove roles from user", - "tags": [ - "Security" - ], + "tags": ["Security"], "args": [ { "name": ":user_id", @@ -12442,9 +11985,7 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.syscheck_controller.delete_syscheck_agent", "description": "Clear file integrity monitoring scan results for a specified agent. Only available for agents < 3.12.0, it doesn't apply for more recent ones", "summary": "Clear results", - "tags": [ - "Syscheck" - ], + "tags": ["Syscheck"], "args": [ { "name": ":agent_id", @@ -12479,4 +12020,4 @@ } ] } -] \ No newline at end of file +] diff --git a/plugins/main/common/api-info/security-actions.json b/plugins/main/common/api-info/security-actions.json index a59f0009dd..7e8fb721a7 100644 --- a/plugins/main/common/api-info/security-actions.json +++ b/plugins/main/common/api-info/security-actions.json @@ -1,57 +1,30 @@ { "active-response:command": { "description": "Execute active response commands in the agents", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "active-response:command" - ], - "resources": [ - "agent:id:001", - "agent:group:atlantic" - ], + "actions": ["active-response:command"], + "resources": ["agent:id:001", "agent:group:atlantic"], "effect": "allow" }, - "related_endpoints": [ - "PUT /active-response" - ] + "related_endpoints": ["PUT /active-response"] }, "agent:delete": { "description": "Delete agents", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "agent:delete" - ], - "resources": [ - "agent:id:010", - "agent:group:pacific" - ], + "actions": ["agent:delete"], + "resources": ["agent:id:010", "agent:group:pacific"], "effect": "allow" }, - "related_endpoints": [ - "DELETE /agents" - ] + "related_endpoints": ["DELETE /agents"] }, "agent:read": { "description": "Access agents information (id, name, group, last keep alive, etc)", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "agent:read" - ], - "resources": [ - "agent:id:*" - ], + "actions": ["agent:read"], + "resources": ["agent:id:*"], "effect": "allow" }, "related_endpoints": [ @@ -72,16 +45,10 @@ }, "agent:create": { "description": "Create new agents", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "agent:create" - ], - "resources": [ - "*:*:*" - ], + "actions": ["agent:create"], + "resources": ["*:*:*"], "effect": "allow" }, "related_endpoints": [ @@ -92,18 +59,10 @@ }, "agent:modify_group": { "description": "Change the group of agents", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "agent:modify_group" - ], - "resources": [ - "agent:id:004", - "agent:group:us-east" - ], + "actions": ["agent:modify_group"], + "resources": ["agent:id:004", "agent:group:us-east"], "effect": "allow" }, "related_endpoints": [ @@ -116,16 +75,10 @@ }, "group:modify_assignments": { "description": "Change the agents assigned to the group", - "resources": [ - "group:id" - ], + "resources": ["group:id"], "example": { - "actions": [ - "group:modify_assignments" - ], - "resources": [ - "group:id:*" - ], + "actions": ["group:modify_assignments"], + "resources": ["group:id:*"], "effect": "allow" }, "related_endpoints": [ @@ -138,18 +91,10 @@ }, "agent:restart": { "description": "Restart agents", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "agent:restart" - ], - "resources": [ - "agent:id:050", - "agent:id:049" - ], + "actions": ["agent:restart"], + "resources": ["agent:id:050", "agent:id:049"], "effect": "deny" }, "related_endpoints": [ @@ -161,18 +106,10 @@ }, "agent:upgrade": { "description": "Upgrade the version of the agents", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "agent:upgrade" - ], - "resources": [ - "agent:id:001", - "agent:group:mediterranean" - ], + "actions": ["agent:upgrade"], + "resources": ["agent:id:001", "agent:group:mediterranean"], "effect": "allow" }, "related_endpoints": [ @@ -183,34 +120,20 @@ }, "group:delete": { "description": "Delete agent groups", - "resources": [ - "group:id" - ], + "resources": ["group:id"], "example": { - "actions": [ - "group:delete" - ], - "resources": [ - "group:id:*" - ], + "actions": ["group:delete"], + "resources": ["group:id:*"], "effect": "allow" }, - "related_endpoints": [ - "DELETE /groups" - ] + "related_endpoints": ["DELETE /groups"] }, "group:read": { "description": "Access agent groups information (id, name, agents, etc)", - "resources": [ - "group:id" - ], + "resources": ["group:id"], "example": { - "actions": [ - "group:create" - ], - "resources": [ - "group:id:*" - ], + "actions": ["group:create"], + "resources": ["group:id:*"], "effect": "allow" }, "related_endpoints": [ @@ -218,60 +141,37 @@ "GET /groups/{group_id}/agents", "GET /groups/{group_id}/configuration", "GET /groups/{group_id}/files", - "GET /groups/{group_id}/files/{file_name}/json", - "GET /groups/{group_id}/files/{file_name}/xml", + "GET /groups/{group_id}/files/{file_name}", + "GET /groups/{group_id}/files/{file_name}?raw=true", "GET /overview/agents" ] }, "group:create": { "description": "Create new agent groups", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "group:create" - ], - "resources": [ - "*:*:*" - ], + "actions": ["group:create"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "POST /groups" - ] + "related_endpoints": ["POST /groups"] }, "group:update_config": { "description": "Change the configuration of agent groups", - "resources": [ - "group:id" - ], + "resources": ["group:id"], "example": { - "actions": [ - "group:update_config" - ], - "resources": [ - "group:id:*" - ], + "actions": ["group:update_config"], + "resources": ["group:id:*"], "effect": "deny" }, - "related_endpoints": [ - "PUT /groups/{group_id}/configuration" - ] + "related_endpoints": ["PUT /groups/{group_id}/configuration"] }, "cluster:read": { "description": "Read Wazuh's cluster nodes configuration", - "resources": [ - "node:id" - ], + "resources": ["node:id"], "example": { - "actions": [ - "cluster:read" - ], - "resources": [ - "node:id:worker1", - "node:id:worker3" - ], + "actions": ["cluster:read"], + "resources": ["node:id:worker1", "node:id:worker3"], "effect": "deny" }, "related_endpoints": [ @@ -299,39 +199,20 @@ }, "agent:reconnect": { "description": "Force reconnect agents", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "agent:reconnect" - ], - "resources": [ - "agent:id:050", - "agent:id:049" - ], + "actions": ["agent:reconnect"], + "resources": ["agent:id:050", "agent:id:049"], "effect": "deny" }, - "related_endpoints": [ - "PUT /agents/reconnect" - ] + "related_endpoints": ["PUT /agents/reconnect"] }, "ciscat:read": { "description": "Access CIS-CAT results for agents", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "ciscat:read" - ], - "resources": [ - "agent:id:001", - "agent:id:003", - "agent:group:default" - ], + "actions": ["ciscat:read"], + "resources": ["agent:id:001", "agent:id:003", "agent:group:default"], "effect": "deny" }, "related_endpoints": [ @@ -341,89 +222,50 @@ }, "cluster:status": { "description": "Check Wazuh's cluster general status", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "cluster:status" - ], - "resources": [ - "*:*:*" - ], + "actions": ["cluster:status"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "GET /cluster/status" - ] + "related_endpoints": ["GET /cluster/status"] }, "cluster:read_api_config": { "description": "Check Wazuh's cluster nodes API configuration", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "cluster:read_api_config" - ], - "resources": [ - "node:id:worker1", - "node:id:worker3" - ], + "actions": ["cluster:read_api_config"], + "resources": ["node:id:worker1", "node:id:worker3"], "effect": "allow" }, - "related_endpoints": [ - "GET /cluster/api/config" - ] + "related_endpoints": ["GET /cluster/api/config"] }, "cluster:update_config": { "description": "Change the Wazuh's cluster node configuration", - "resources": [ - "node:id" - ], + "resources": ["node:id"], "example": { - "actions": [ - "cluster:update_config" - ], - "resources": [ - "node:id:worker1" - ], + "actions": ["cluster:update_config"], + "resources": ["node:id:worker1"], "effect": "allow" }, - "related_endpoints": [ - "PUT /cluster/{node_id}/configuration" - ] + "related_endpoints": ["PUT /cluster/{node_id}/configuration"] }, "cluster:restart": { "description": "Restart Wazuh's cluster nodes", - "resources": [ - "node:id" - ], + "resources": ["node:id"], "example": { - "actions": [ - "cluster:restart" - ], - "resources": [ - "node:id:worker1" - ], + "actions": ["cluster:restart"], + "resources": ["node:id:worker1"], "effect": "allow" }, - "related_endpoints": [ - "PUT /cluster/restart" - ] + "related_endpoints": ["PUT /cluster/restart"] }, "lists:read": { "description": "Read cdb lists files", - "resources": [ - "list:file" - ], + "resources": ["list:file"], "example": { - "actions": [ - "lists:read" - ], - "resources": [ - "list:file:audit-keys" - ], + "actions": ["lists:read"], + "resources": ["list:file:audit-keys"], "effect": "deny" }, "related_endpoints": [ @@ -434,34 +276,20 @@ }, "lists:update": { "description": "Update or upload cdb lists files", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "lists:update" - ], - "resources": [ - "*:*:*" - ], + "actions": ["lists:update"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /lists/files/{filename}" - ] + "related_endpoints": ["PUT /lists/files/{filename}"] }, "lists:delete": { "description": "Delete cdb lists files", - "resources": [ - "list:file" - ], + "resources": ["list:file"], "example": { - "actions": [ - "lists:delete" - ], - "resources": [ - "list:file:audit-keys" - ], + "actions": ["lists:delete"], + "resources": ["list:file:audit-keys"], "effect": "deny" }, "related_endpoints": [ @@ -471,35 +299,20 @@ }, "logtest:run": { "description": "Run logtest tool or end a logtest session", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "logtest:run" - ], - "resources": [ - "*:*:*" - ], + "actions": ["logtest:run"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /logtest", - "DELETE /logtest/sessions/{token}" - ] + "related_endpoints": ["PUT /logtest", "DELETE /logtest/sessions/{token}"] }, "manager:read": { "description": "Read Wazuh manager configuration", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "manager:read" - ], - "resources": [ - "*:*:*" - ], + "actions": ["manager:read"], + "resources": ["*:*:*"], "effect": "allow" }, "related_endpoints": [ @@ -521,70 +334,40 @@ }, "manager:update_config": { "description": "Update current Wazuh manager configuration", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "manager:update_config" - ], - "resources": [ - "*:*:*" - ], + "actions": ["manager:update_config"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /manager/configuration" - ] + "related_endpoints": ["PUT /manager/configuration"] }, "manager:read_api_config": { "description": "Read Wazuh manager API configuration", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "manager:read_api_config" - ], - "resources": [ - "*:*:*" - ], + "actions": ["manager:read_api_config"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "GET /manager/api/config" - ] + "related_endpoints": ["GET /manager/api/config"] }, "manager:restart": { "description": "Restart Wazuh managers", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "manager:restart" - ], - "resources": [ - "*:*:*" - ], + "actions": ["manager:restart"], + "resources": ["*:*:*"], "effect": "deny" }, - "related_endpoints": [ - "PUT /manager/restart" - ] + "related_endpoints": ["PUT /manager/restart"] }, "mitre:read": { "description": "Access information from MITRE database", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "mitre:read" - ], - "resources": [ - "*:*:*" - ], + "actions": ["mitre:read"], + "resources": ["*:*:*"], "effect": "allow" }, "related_endpoints": [ @@ -599,36 +382,20 @@ }, "rootcheck:run": { "description": "Run agents rootcheck scan", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "rootcheck:run" - ], - "resources": [ - "agent:id:*" - ], + "actions": ["rootcheck:run"], + "resources": ["agent:id:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /rootcheck" - ] + "related_endpoints": ["PUT /rootcheck"] }, "rootcheck:read": { "description": "Access information from agents rootcheck database", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "rootcheck:read" - ], - "resources": [ - "agent:id:011" - ], + "actions": ["rootcheck:read"], + "resources": ["agent:id:011"], "effect": "allow" }, "related_endpoints": [ @@ -638,17 +405,10 @@ }, "rootcheck:clear": { "description": "Clear the agents rootcheck database", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "rootcheck:clear" - ], - "resources": [ - "agent:id:*" - ], + "actions": ["rootcheck:clear"], + "resources": ["agent:id:*"], "effect": "deny" }, "related_endpoints": [ @@ -658,16 +418,10 @@ }, "rules:read": { "description": "Read rules files", - "resources": [ - "rule:file" - ], + "resources": ["rule:file"], "example": { - "actions": [ - "rules:read" - ], - "resources": [ - "rule:file:0610-win-ms_logs_rules.xml" - ], + "actions": ["rules:read"], + "resources": ["rule:file:0610-win-ms_logs_rules.xml"], "effect": "allow" }, "related_endpoints": [ @@ -680,34 +434,20 @@ }, "rules:update": { "description": "Update or upload custom rule files", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "rules:update" - ], - "resources": [ - "*:*:*" - ], + "actions": ["rules:update"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /rules/files/{filename}" - ] + "related_endpoints": ["PUT /rules/files/{filename}"] }, "rules:delete": { "description": "Delete custom rule files", - "resources": [ - "rule:file" - ], + "resources": ["rule:file"], "example": { - "actions": [ - "rules:delete" - ], - "resources": [ - "rule:file:0610-win-ms_logs_rules.xml" - ], + "actions": ["rules:delete"], + "resources": ["rule:file:0610-win-ms_logs_rules.xml"], "effect": "allow" }, "related_endpoints": [ @@ -717,17 +457,10 @@ }, "sca:read": { "description": "Access agents security configuration assessment", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "sca:read" - ], - "resources": [ - "agent:id:*" - ], + "actions": ["sca:read"], + "resources": ["agent:id:*"], "effect": "allow" }, "related_endpoints": [ @@ -737,37 +470,20 @@ }, "syscheck:run": { "description": "Run agents syscheck scan", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "syscheck:run" - ], - "resources": [ - "agent:id:*" - ], + "actions": ["syscheck:run"], + "resources": ["agent:id:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /syscheck" - ] + "related_endpoints": ["PUT /syscheck"] }, "syscheck:read": { "description": "Access information from agents syscheck database", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "syscheck:read" - ], - "resources": [ - "agent:id:011", - "agent:group:us-west" - ], + "actions": ["syscheck:read"], + "resources": ["agent:id:011", "agent:group:us-west"], "effect": "allow" }, "related_endpoints": [ @@ -777,17 +493,10 @@ }, "syscheck:clear": { "description": "Clear the agents syscheck database", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "syscheck:clear" - ], - "resources": [ - "agent:id:*" - ], + "actions": ["syscheck:clear"], + "resources": ["agent:id:*"], "effect": "deny" }, "related_endpoints": [ @@ -797,16 +506,10 @@ }, "decoders:read": { "description": "Read decoders files", - "resources": [ - "decoder:file" - ], + "resources": ["decoder:file"], "example": { - "actions": [ - "decoders:read" - ], - "resources": [ - "decoder:file:*" - ], + "actions": ["decoders:read"], + "resources": ["decoder:file:*"], "effect": "allow" }, "related_endpoints": [ @@ -818,34 +521,20 @@ }, "decoders:update": { "description": "Update or upload custom decoder files", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "decoders:update" - ], - "resources": [ - "*:*:*" - ], + "actions": ["decoders:update"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /decoders/files/{filename}" - ] + "related_endpoints": ["PUT /decoders/files/{filename}"] }, "decoders:delete": { "description": "Delete custom decoder files", - "resources": [ - "decoder:file" - ], + "resources": ["decoder:file"], "example": { - "actions": [ - "decoders:delete" - ], - "resources": [ - "decoder:file:local_decoder.xml" - ], + "actions": ["decoders:delete"], + "resources": ["decoder:file:local_decoder.xml"], "effect": "allow" }, "related_endpoints": [ @@ -855,17 +544,10 @@ }, "syscollector:read": { "description": "Access agents syscollector information", - "resources": [ - "agent:id", - "agent:group" - ], + "resources": ["agent:id", "agent:group"], "example": { - "actions": [ - "syscollector:read" - ], - "resources": [ - "agent:id:*" - ], + "actions": ["syscollector:read"], + "resources": ["agent:id:*"], "effect": "allow" }, "related_endpoints": [ @@ -891,40 +573,20 @@ }, "security:edit_run_as": { "description": "Change the value of the allow_run_as flag for a user", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "security:edit_run_as" - ], - "resources": [ - "*:*:*" - ], + "actions": ["security:edit_run_as"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /security/users/{user_id}/run_as" - ] + "related_endpoints": ["PUT /security/users/{user_id}/run_as"] }, "security:read": { "description": "Access information about system security resources", - "resources": [ - "policy:id", - "role:id", - "user:id", - "rule:id" - ], + "resources": ["policy:id", "role:id", "user:id", "rule:id"], "example": { - "actions": [ - "security:read" - ], - "resources": [ - "policy:id:*", - "role:id:2", - "user:id:5", - "rule:id:3" - ], + "actions": ["security:read"], + "resources": ["policy:id:*", "role:id:2", "user:id:5", "rule:id:3"], "effect": "allow" }, "related_endpoints": [ @@ -936,40 +598,20 @@ }, "security:create_user": { "description": "Create new system users", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "security:create_user" - ], - "resources": [ - "*:*:*" - ], + "actions": ["security:create_user"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "POST /security/users" - ] + "related_endpoints": ["POST /security/users"] }, "security:delete": { "description": "Delete system security resources", - "resources": [ - "policy:id", - "role:id", - "user:id", - "rule:id" - ], + "resources": ["policy:id", "role:id", "user:id", "rule:id"], "example": { - "actions": [ - "security:update" - ], - "resources": [ - "policy:id:*", - "role:id:3", - "user:id:4", - "rule:id:2" - ], + "actions": ["security:update"], + "resources": ["policy:id:*", "role:id:3", "user:id:4", "rule:id:2"], "effect": "deny" }, "related_endpoints": [ @@ -984,22 +626,10 @@ }, "security:update": { "description": "Update the information of system security resources", - "resources": [ - "policy:id", - "role:id", - "user:id", - "rule:id" - ], + "resources": ["policy:id", "role:id", "user:id", "rule:id"], "example": { - "actions": [ - "security:update" - ], - "resources": [ - "policy:id:*", - "role:id:4", - "user:id:3", - "rule:id:4" - ], + "actions": ["security:update"], + "resources": ["policy:id:*", "role:id:4", "user:id:3", "rule:id:4"], "effect": "deny" }, "related_endpoints": [ @@ -1014,16 +644,10 @@ }, "security:create": { "description": "Create new system security resources", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "security:create" - ], - "resources": [ - "*:*:*" - ], + "actions": ["security:create"], + "resources": ["*:*:*"], "effect": "deny" }, "related_endpoints": [ @@ -1034,75 +658,42 @@ }, "security:read_config": { "description": "Read current system security configuration", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "security:read_config" - ], - "resources": [ - "*:*:*" - ], + "actions": ["security:read_config"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "GET /security/config" - ] + "related_endpoints": ["GET /security/config"] }, "security:update_config": { "description": "Update current system security configuration", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "security:update_config" - ], - "resources": [ - "*:*:*" - ], + "actions": ["security:update_config"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "PUT /security/config", - "DELETE /security/config" - ] + "related_endpoints": ["PUT /security/config", "DELETE /security/config"] }, "task:status": { "description": "Access task's status information", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "task:status" - ], - "resources": [ - "*:*:*" - ], + "actions": ["task:status"], + "resources": ["*:*:*"], "effect": "deny" }, - "related_endpoints": [ - "GET /tasks/status" - ] + "related_endpoints": ["GET /tasks/status"] }, "event:ingest": { "description": "Ingest events", - "resources": [ - "*:*" - ], + "resources": ["*:*"], "example": { - "actions": [ - "event:ingest" - ], - "resources": [ - "*:*:*" - ], + "actions": ["event:ingest"], + "resources": ["*:*:*"], "effect": "allow" }, - "related_endpoints": [ - "POST /events" - ] + "related_endpoints": ["POST /events"] } -} \ No newline at end of file +} diff --git a/plugins/main/common/config-equivalences.js b/plugins/main/common/config-equivalences.js deleted file mode 100644 index 529a0346b2..0000000000 --- a/plugins/main/common/config-equivalences.js +++ /dev/null @@ -1,219 +0,0 @@ -import { ASSETS_PUBLIC_URL, PLUGIN_PLATFORM_NAME } from './constants'; - -export const configEquivalences = { - pattern: - "Default index pattern to use on the app. If there's no valid index pattern, the app will automatically create one with the name indicated in this option.", - 'customization.logo.app': `Set the name of the app logo stored at ${ASSETS_PUBLIC_URL}. It is used while the user is logging into Wazuh API.`, - 'customization.logo.healthcheck': `Set the name of the health-check logo stored at ${ASSETS_PUBLIC_URL}`, - 'customization.logo.reports': `Set the name of the reports logo (.png) stored at ${ASSETS_PUBLIC_URL}`, - 'checks.pattern': - 'Enable or disable the index pattern health check when opening the app.', - 'checks.template': - 'Enable or disable the template health check when opening the app.', - 'checks.api': 'Enable or disable the API health check when opening the app.', - 'checks.setup': - 'Enable or disable the setup health check when opening the app.', - 'checks.fields': - 'Enable or disable the known fields health check when opening the app.', - 'checks.metaFields': `Change the default value of the ${PLUGIN_PLATFORM_NAME} metaField configuration`, - 'checks.timeFilter': `Change the default value of the ${PLUGIN_PLATFORM_NAME} timeFilter configuration`, - 'checks.maxBuckets': `Change the default value of the ${PLUGIN_PLATFORM_NAME} max buckets configuration`, - timeout: - 'Maximum time, in milliseconds, the app will wait for an API response when making requests to it. It will be ignored if the value is set under 1500 milliseconds.', - 'ip.selector': - 'Define if the user is allowed to change the selected index pattern directly from the top menu bar.', - 'ip.ignore': - 'Disable certain index pattern names from being available in index pattern selector from the Wazuh app.', - 'wazuh.monitoring.enabled': - 'Enable or disable the wazuh-monitoring index creation and/or visualization.', - 'wazuh.monitoring.frequency': - 'Frequency, in seconds, of API requests to get the state of the agents and create a new document in the wazuh-monitoring index with this data.', - 'wazuh.monitoring.shards': - 'Define the number of shards to use for the wazuh-monitoring-* indices.', - 'wazuh.monitoring.replicas': - 'Define the number of replicas to use for the wazuh-monitoring-* indices.', - 'wazuh.monitoring.creation': - 'Define the interval in which a new wazuh-monitoring index will be created.', - 'wazuh.monitoring.pattern': - 'Default index pattern to use for Wazuh monitoring.', - hideManagerAlerts: 'Hide the alerts of the manager in every dashboard.', - 'enrollment.dns': - 'Specifies the Wazuh registration server, used for the agent enrollment.', - 'enrollment.password': - 'Specifies the password used to authenticate during the agent enrollment.', - 'cron.prefix': 'Define the index prefix of predefined jobs.', - 'cron.statistics.status': 'Enable or disable the statistics tasks.', - 'cron.statistics.apis': - 'Enter the ID of the hosts you want to save data from, leave this empty to run the task on every host.', - 'cron.statistics.interval': - 'Define the frequency of task execution using cron schedule expressions.', - 'cron.statistics.index.name': - 'Define the name of the index in which the documents will be saved.', - 'cron.statistics.index.creation': - 'Define the interval in which a new index will be created.', - 'cron.statistics.index.shards': - 'Define the number of shards to use for the statistics indices.', - 'cron.statistics.index.replicas': - 'Define the number of replicas to use for the statistics indices.', - 'alerts.sample.prefix': - 'Define the index name prefix of sample alerts. It must match the template used by the index pattern to avoid unknown fields in dashboards.', - 'vulnerabilities.pattern': - 'Default index pattern to use for vulnerabilities.', -}; - -export const nameEquivalence = { - pattern: 'Index pattern', - 'customization.logo.app': 'Logo App', - 'customization.logo.healthcheck': 'Logo Health Check', - 'customization.logo.reports': 'Logo Reports', - 'checks.pattern': 'Index pattern', - 'checks.template': 'Index template', - 'checks.api': 'API connection', - 'checks.setup': 'API version', - 'checks.fields': 'Known fields', - 'checks.metaFields': 'Remove meta fields', - 'checks.timeFilter': 'Set time filter to 24h', - 'checks.maxBuckets': 'Set max buckets to 200000', - timeout: 'Request timeout', - 'ip.selector': 'IP selector', - 'ip.ignore': 'IP ignore', - 'wazuh.monitoring.enabled': 'Status', - 'wazuh.monitoring.frequency': 'Frequency', - 'wazuh.monitoring.shards': 'Index shards', - 'wazuh.monitoring.replicas': 'Index replicas', - 'wazuh.monitoring.creation': 'Index creation', - 'wazuh.monitoring.pattern': 'Index pattern', - hideManagerAlerts: 'Hide manager alerts', - 'enrollment.dns': 'Enrollment DNS', - 'cron.prefix': 'Cron prefix', - 'cron.statistics.status': 'Status', - 'cron.statistics.apis': 'Includes apis', - 'cron.statistics.interval': 'Interval', - 'cron.statistics.index.name': 'Index name', - 'cron.statistics.index.creation': 'Index creation', - 'cron.statistics.index.shards': 'Index shards', - 'cron.statistics.index.replicas': 'Index replicas', - 'alerts.sample.prefix': 'Sample alerts prefix', - 'vulnerabilities.pattern': 'Index pattern', - 'checks.vulnerabilities.pattern': 'Vulnerabilities index pattern', - 'fim.pattern': 'Index pattern', - 'checks.fim.pattern': 'Fim index pattern', -}; - -const HEALTH_CHECK = 'Health Check'; -const GENERAL = 'General'; -const SECURITY = 'Security'; -const MONITORING = 'Monitoring'; -const STATISTICS = 'Statistics'; -const VULNERABILITIES = 'Vulnerabilities'; -const CUSTOMIZATION = 'Logo Customization'; -export const categoriesNames = [ - HEALTH_CHECK, - GENERAL, - SECURITY, - MONITORING, - STATISTICS, - VULNERABILITIES, - CUSTOMIZATION, -]; - -export const categoriesEquivalence = { - pattern: GENERAL, - 'customization.logo.app': CUSTOMIZATION, - 'customization.logo.healthcheck': CUSTOMIZATION, - 'customization.logo.reports': CUSTOMIZATION, - 'checks.pattern': HEALTH_CHECK, - 'checks.template': HEALTH_CHECK, - 'checks.api': HEALTH_CHECK, - 'checks.setup': HEALTH_CHECK, - 'checks.fields': HEALTH_CHECK, - 'checks.metaFields': HEALTH_CHECK, - 'checks.timeFilter': HEALTH_CHECK, - 'checks.maxBuckets': HEALTH_CHECK, - timeout: GENERAL, - 'ip.selector': GENERAL, - 'ip.ignore': GENERAL, - 'wazuh.monitoring.enabled': MONITORING, - 'wazuh.monitoring.frequency': MONITORING, - 'wazuh.monitoring.shards': MONITORING, - 'wazuh.monitoring.replicas': MONITORING, - 'wazuh.monitoring.creation': MONITORING, - 'wazuh.monitoring.pattern': MONITORING, - hideManagerAlerts: GENERAL, - 'enrollment.dns': GENERAL, - 'cron.prefix': GENERAL, - 'cron.statistics.status': STATISTICS, - 'cron.statistics.apis': STATISTICS, - 'cron.statistics.interval': STATISTICS, - 'cron.statistics.index.name': STATISTICS, - 'cron.statistics.index.creation': STATISTICS, - 'cron.statistics.index.shards': STATISTICS, - 'cron.statistics.index.replicas': STATISTICS, - 'alerts.sample.prefix': GENERAL, - 'vulnerabilities.pattern': VULNERABILITIES, - 'checks.vulnerabilities.pattern': HEALTH_CHECK, -}; - -const TEXT = 'text'; -const NUMBER = 'number'; -const LIST = 'list'; -const BOOLEAN = 'boolean'; -const ARRAY = 'array'; -const INTERVAL = 'interval'; - -export const formEquivalence = { - pattern: { type: TEXT }, - 'customization.logo.app': { type: TEXT }, - 'customization.logo.healthcheck': { type: TEXT }, - 'customization.logo.reports': { type: TEXT }, - 'checks.pattern': { type: BOOLEAN }, - 'checks.template': { type: BOOLEAN }, - 'checks.api': { type: BOOLEAN }, - 'checks.setup': { type: BOOLEAN }, - 'checks.fields': { type: BOOLEAN }, - 'checks.metaFields': { type: BOOLEAN }, - 'checks.timeFilter': { type: BOOLEAN }, - 'checks.maxBuckets': { type: BOOLEAN }, - timeout: { type: NUMBER }, - 'ip.selector': { type: BOOLEAN }, - 'ip.ignore': { type: ARRAY }, - 'wazuh.monitoring.enabled': { type: BOOLEAN }, - 'wazuh.monitoring.frequency': { type: NUMBER }, - 'wazuh.monitoring.shards': { type: NUMBER }, - 'wazuh.monitoring.replicas': { type: NUMBER }, - 'wazuh.monitoring.creation': { - type: LIST, - params: { - options: [ - { text: 'Hourly', value: 'h' }, - { text: 'Daily', value: 'd' }, - { text: 'Weekly', value: 'w' }, - { text: 'Monthly', value: 'm' }, - ], - }, - }, - 'wazuh.monitoring.pattern': { type: TEXT }, - hideManagerAlerts: { type: BOOLEAN }, - 'enrollment.dns': { type: TEXT }, - 'cron.prefix': { type: TEXT }, - 'cron.statistics.status': { type: BOOLEAN }, - 'cron.statistics.apis': { type: ARRAY }, - 'cron.statistics.interval': { type: INTERVAL }, - 'cron.statistics.index.name': { type: TEXT }, - 'cron.statistics.index.creation': { - type: LIST, - params: { - options: [ - { text: 'Hourly', value: 'h' }, - { text: 'Daily', value: 'd' }, - { text: 'Weekly', value: 'w' }, - { text: 'Monthly', value: 'm' }, - ], - }, - }, - 'cron.statistics.index.shards': { type: NUMBER }, - 'cron.statistics.index.replicas': { type: NUMBER }, - 'alerts.sample.prefix': { type: TEXT }, - 'vulnerabilities.pattern': { type: TEXT }, - 'checks.vulnerabilities.pattern': { type: BOOLEAN }, -}; diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index d1a9ea4fd3..fdd4e7631b 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -11,8 +11,6 @@ */ import path from 'path'; import { version } from '../package.json'; -import { validate as validateNodeCronInterval } from 'node-cron'; -import { SettingsValidator } from '../common/services/settings-validator'; // Plugin export const PLUGIN_VERSION = version; @@ -51,6 +49,10 @@ export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *'; // Wazuh vulnerabilities export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities'; export const WAZUH_INDEX_TYPE_VULNERABILITIES = 'vulnerabilities'; +export const VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER = { + enabled: 'wazuh.cluster.name', + disabled: 'wazuh.manager.name', +}; // Wazuh fim export const WAZUH_FIM_PATTERN = 'wazuh-states-fim'; @@ -58,10 +60,6 @@ export const WAZUH_FIM_PATTERN = 'wazuh-states-fim'; // Job - Wazuh initialize export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana'; -// Permissions -export const WAZUH_ROLE_ADMINISTRATOR_ID = 1; -export const WAZUH_ROLE_ADMINISTRATOR_NAME = 'administrator'; - // Sample data export const WAZUH_SAMPLE_ALERT_PREFIX = 'wazuh-alerts-4.x-'; export const WAZUH_SAMPLE_ALERTS_INDEX_SHARDS = 1; @@ -131,10 +129,6 @@ export const WAZUH_DATA_CONFIG_DIRECTORY_PATH = path.join( WAZUH_DATA_ABSOLUTE_PATH, 'config', ); -export const WAZUH_DATA_CONFIG_APP_PATH = path.join( - WAZUH_DATA_CONFIG_DIRECTORY_PATH, - 'wazuh.yml', -); export const WAZUH_DATA_CONFIG_REGISTRY_PATH = path.join( WAZUH_DATA_CONFIG_DIRECTORY_PATH, 'wazuh-registry.json', @@ -230,6 +224,7 @@ export enum WAZUH_MENU_SETTINGS_SECTIONS_ID { } export const AUTHORIZED_AGENTS = 'authorized-agents'; +export const DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER = 'exclude-server'; // Wazuh links export const WAZUH_LINK_GITHUB = 'https://github.com/wazuh'; @@ -387,73 +382,6 @@ export const NOT_TIME_FIELD_NAME_INDEX_PATTERN = // Customization export const CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES = 1048576; -// Plugin settings -export enum SettingCategory { - GENERAL, - HEALTH_CHECK, - MONITORING, - STATISTICS, - VULNERABILITIES, - SECURITY, - CUSTOMIZATION, -} - -type TPluginSettingOptionsTextArea = { - maxRows?: number; - minRows?: number; - maxLength?: number; -}; - -type TPluginSettingOptionsSelect = { - select: { text: string; value: any }[]; -}; - -type TPluginSettingOptionsEditor = { - editor: { - language: string; - }; -}; - -type TPluginSettingOptionsFile = { - file: { - type: 'image'; - extensions?: string[]; - size?: { - maxBytes?: number; - minBytes?: number; - }; - recommended?: { - dimensions?: { - width: number; - height: number; - unit: string; - }; - }; - store?: { - relativePathFileSystem: string; - filename: string; - resolveStaticURL: (filename: string) => string; - }; - }; -}; - -type TPluginSettingOptionsNumber = { - number: { - min?: number; - max?: number; - integer?: boolean; - }; -}; - -type TPluginSettingOptionsSwitch = { - switch: { - values: { - disabled: { label?: string; value: any }; - enabled: { label?: string; value: any }; - }; - }; -}; - export enum EpluginSettingType { text = 'text', textarea = 'textarea', @@ -462,1309 +390,11 @@ export enum EpluginSettingType { editor = 'editor', select = 'select', filepicker = 'filepicker', + password = 'password', + arrayOf = 'arrayOf', + custom = 'custom', } -export type TPluginSetting = { - // Define the text displayed in the UI. - title: string; - // Description. - description: string; - // Category. - category: SettingCategory; - // Type. - type: EpluginSettingType; - // Default value. - defaultValue: any; - // Default value if it is not set. It has preference over `default`. - defaultValueIfNotSet?: any; - // Configurable from the configuration file. - isConfigurableFromFile: boolean; - // Configurable from the UI (Settings/Configuration). - isConfigurableFromUI: boolean; - // Modify the setting requires running the plugin health check (frontend). - requiresRunningHealthCheck?: boolean; - // Modify the setting requires reloading the browser tab (frontend). - requiresReloadingBrowserTab?: boolean; - // Modify the setting requires restarting the plugin platform to take effect. - requiresRestartingPluginPlatform?: boolean; - // Define options related to the `type`. - options?: - | TPluginSettingOptionsEditor - | TPluginSettingOptionsFile - | TPluginSettingOptionsNumber - | TPluginSettingOptionsSelect - | TPluginSettingOptionsSwitch - | TPluginSettingOptionsTextArea; - // Transform the input value. The result is saved in the form global state of Settings/Configuration - uiFormTransformChangedInputValue?: (value: any) => any; - // Transform the configuration value or default as initial value for the input in Settings/Configuration - uiFormTransformConfigurationValueToInputValue?: (value: any) => any; - // Transform the input value changed in the form of Settings/Configuration and returned in the `changed` property of the hook useForm - uiFormTransformInputValueToConfigurationValue?: (value: any) => any; - // Validate the value in the form of Settings/Configuration. It returns a string if there is some validation error. - validate?: (value: any) => string | undefined; - // Validate function creator to validate the setting in the backend. It uses `schema` of the `@kbn/config-schema` package. - validateBackend?: (schema: any) => (value: unknown) => string | undefined; -}; - -export type TPluginSettingWithKey = TPluginSetting & { key: TPluginSettingKey }; -export type TPluginSettingCategory = { - title: string; - description?: string; - documentationLink?: string; - renderOrder?: number; -}; - -export const PLUGIN_SETTINGS_CATEGORIES: { - [category: number]: TPluginSettingCategory; -} = { - [SettingCategory.HEALTH_CHECK]: { - title: 'Health check', - description: "Checks will be executed by the app's Healthcheck.", - renderOrder: SettingCategory.HEALTH_CHECK, - }, - [SettingCategory.GENERAL]: { - title: 'General', - description: - 'Basic app settings related to alerts index pattern, hide the manager alerts in the dashboards, logs level and more.', - renderOrder: SettingCategory.GENERAL, - }, - [SettingCategory.SECURITY]: { - title: 'Security', - description: 'Application security options such as unauthorized roles.', - renderOrder: SettingCategory.SECURITY, - }, - [SettingCategory.MONITORING]: { - title: 'Task:Monitoring', - description: - 'Options related to the agent status monitoring job and its storage in indexes.', - renderOrder: SettingCategory.MONITORING, - }, - [SettingCategory.STATISTICS]: { - title: 'Task:Statistics', - description: - 'Options related to the daemons manager monitoring job and their storage in indexes.', - renderOrder: SettingCategory.STATISTICS, - }, - [SettingCategory.VULNERABILITIES]: { - title: 'Vulnerabilities', - description: - 'Options related to the agent vulnerabilities monitoring job and its storage in indexes.', - renderOrder: SettingCategory.VULNERABILITIES, - }, - [SettingCategory.CUSTOMIZATION]: { - title: 'Custom branding', - description: - 'If you want to use custom branding elements such as logos, you can do so by editing the settings below.', - documentationLink: 'user-manual/wazuh-dashboard/white-labeling.html', - renderOrder: SettingCategory.CUSTOMIZATION, - }, -}; - -export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { - 'alerts.sample.prefix': { - title: 'Sample alerts prefix', - description: - 'Define the index name prefix of sample alerts. It must match the template used by the index pattern to avoid unknown fields in dashboards.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.text, - defaultValue: WAZUH_SAMPLE_ALERT_PREFIX, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - '*', - ), - ), - validateBackend: function (schema) { - return schema.string({ validate: this.validate }); - }, - }, - 'checks.api': { - title: 'API connection', - description: 'Enable or disable the API health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.fields': { - title: 'Known fields', - description: - 'Enable or disable the known fields health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.maxBuckets': { - title: 'Set max buckets to 200000', - description: - 'Change the default value of the plugin platform max buckets configuration.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.metaFields': { - title: 'Remove meta fields', - description: - 'Change the default value of the plugin platform metaField configuration.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.pattern': { - title: 'Index pattern', - description: - 'Enable or disable the index pattern health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.setup': { - title: 'API version', - description: - 'Enable or disable the setup health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.template': { - title: 'Index template', - description: - 'Enable or disable the template health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.timeFilter': { - title: 'Set time filter to 24h', - description: - 'Change the default value of the plugin platform timeFilter configuration.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.vulnerabilities.pattern': { - title: 'Vulnerabilities index pattern', - description: - 'Enable or disable the vulnerabilities index pattern health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'checks.fim.pattern': { - title: 'Fim index pattern', - description: - 'Enable or disable the fim index pattern health check when opening the app.', - category: SettingCategory.HEALTH_CHECK, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'cron.prefix': { - title: 'Cron prefix', - description: 'Define the index prefix of predefined jobs.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.text, - defaultValue: WAZUH_STATISTICS_DEFAULT_PREFIX, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - '*', - ), - ), - validateBackend: function (schema) { - return schema.string({ validate: this.validate }); - }, - }, - 'cron.statistics.apis': { - title: 'Includes APIs', - description: - 'Enter the ID of the hosts you want to save data from, leave this empty to run the task on every host.', - category: SettingCategory.STATISTICS, - type: EpluginSettingType.editor, - defaultValue: [], - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - editor: { - language: 'json', - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: any): any { - return JSON.stringify(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): any { - try { - return JSON.parse(value); - } catch (error) { - return value; - } - }, - validate: SettingsValidator.json( - SettingsValidator.compose( - SettingsValidator.array( - SettingsValidator.compose( - SettingsValidator.isString, - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - ), - ), - ), - ), - validateBackend: function (schema) { - return schema.arrayOf( - schema.string({ - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - ), - }), - ); - }, - }, - 'cron.statistics.index.creation': { - title: 'Index creation', - description: 'Define the interval in which a new index will be created.', - category: SettingCategory.STATISTICS, - type: EpluginSettingType.select, - options: { - select: [ - { - text: 'Hourly', - value: 'h', - }, - { - text: 'Daily', - value: 'd', - }, - { - text: 'Weekly', - value: 'w', - }, - { - text: 'Monthly', - value: 'm', - }, - ], - }, - defaultValue: WAZUH_STATISTICS_DEFAULT_CREATION, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - validate: function (value) { - return SettingsValidator.literal( - this.options.select.map(({ value }) => value), - )(value); - }, - validateBackend: function (schema) { - return schema.oneOf( - this.options.select.map(({ value }) => schema.literal(value)), - ); - }, - }, - 'cron.statistics.index.name': { - title: 'Index name', - description: - 'Define the name of the index in which the documents will be saved.', - category: SettingCategory.STATISTICS, - type: EpluginSettingType.text, - defaultValue: WAZUH_STATISTICS_DEFAULT_NAME, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - '*', - ), - ), - validateBackend: function (schema) { - return schema.string({ validate: this.validate }); - }, - }, - 'cron.statistics.index.replicas': { - title: 'Index replicas', - description: - 'Define the number of replicas to use for the statistics indices.', - category: SettingCategory.STATISTICS, - type: EpluginSettingType.number, - defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - options: { - number: { - min: 0, - integer: true, - }, - }, - uiFormTransformConfigurationValueToInputValue: function ( - value: number, - ): string { - return String(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): number { - return Number(value); - }, - validate: function (value) { - return SettingsValidator.number(this.options.number)(value); - }, - validateBackend: function (schema) { - return schema.number({ validate: this.validate.bind(this) }); - }, - }, - 'cron.statistics.index.shards': { - title: 'Index shards', - description: - 'Define the number of shards to use for the statistics indices.', - category: SettingCategory.STATISTICS, - type: EpluginSettingType.number, - defaultValue: WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - options: { - number: { - min: 1, - integer: true, - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: number) { - return String(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): number { - return Number(value); - }, - validate: function (value) { - return SettingsValidator.number(this.options.number)(value); - }, - validateBackend: function (schema) { - return schema.number({ validate: this.validate.bind(this) }); - }, - }, - 'cron.statistics.interval': { - title: 'Interval', - description: - 'Define the frequency of task execution using cron schedule expressions.', - category: SettingCategory.STATISTICS, - type: EpluginSettingType.text, - defaultValue: WAZUH_STATISTICS_DEFAULT_CRON_FREQ, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: true, - validate: function (value: string) { - return validateNodeCronInterval(value) - ? undefined - : 'Interval is not valid.'; - }, - validateBackend: function (schema) { - return schema.string({ validate: this.validate }); - }, - }, - 'cron.statistics.status': { - title: 'Status', - description: 'Enable or disable the statistics tasks.', - category: SettingCategory.STATISTICS, - type: EpluginSettingType.switch, - defaultValue: WAZUH_STATISTICS_DEFAULT_STATUS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'customization.enabled': { - title: 'Status', - description: 'Enable or disable the customization.', - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresReloadingBrowserTab: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'customization.logo.app': { - title: 'App main logo', - description: `This logo is used as loading indicator while the user is logging into Wazuh API.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.filepicker, - defaultValue: '', - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - file: { - type: 'image', - extensions: ['.jpeg', '.jpg', '.png', '.svg'], - size: { - maxBytes: - CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, - }, - recommended: { - dimensions: { - width: 300, - height: 70, - unit: 'px', - }, - }, - store: { - relativePathFileSystem: 'public/assets/custom/images', - filename: 'customization.logo.app', - resolveStaticURL: (filename: string) => - `custom/images/${filename}?v=${Date.now()}`, - // ?v=${Date.now()} is used to force the browser to reload the image when a new file is uploaded - }, - }, - }, - validate: function (value) { - return SettingsValidator.compose( - SettingsValidator.filePickerFileSize({ - ...this.options.file.size, - meaningfulUnit: true, - }), - SettingsValidator.filePickerSupportedExtensions( - this.options.file.extensions, - ), - )(value); - }, - }, - 'customization.logo.healthcheck': { - title: 'Healthcheck logo', - description: `This logo is displayed during the Healthcheck routine of the app.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.filepicker, - defaultValue: '', - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - file: { - type: 'image', - extensions: ['.jpeg', '.jpg', '.png', '.svg'], - size: { - maxBytes: - CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, - }, - recommended: { - dimensions: { - width: 300, - height: 70, - unit: 'px', - }, - }, - store: { - relativePathFileSystem: 'public/assets/custom/images', - filename: 'customization.logo.healthcheck', - resolveStaticURL: (filename: string) => - `custom/images/${filename}?v=${Date.now()}`, - // ?v=${Date.now()} is used to force the browser to reload the image when a new file is uploaded - }, - }, - }, - validate: function (value) { - return SettingsValidator.compose( - SettingsValidator.filePickerFileSize({ - ...this.options.file.size, - meaningfulUnit: true, - }), - SettingsValidator.filePickerSupportedExtensions( - this.options.file.extensions, - ), - )(value); - }, - }, - 'customization.logo.reports': { - title: 'PDF reports logo', - description: `This logo is used in the PDF reports generated by the app. It's placed at the top left corner of every page of the PDF.`, - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.filepicker, - defaultValue: '', - defaultValueIfNotSet: REPORTS_LOGO_IMAGE_ASSETS_RELATIVE_PATH, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - file: { - type: 'image', - extensions: ['.jpeg', '.jpg', '.png'], - size: { - maxBytes: - CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, - }, - recommended: { - dimensions: { - width: 190, - height: 40, - unit: 'px', - }, - }, - store: { - relativePathFileSystem: 'public/assets/custom/images', - filename: 'customization.logo.reports', - resolveStaticURL: (filename: string) => `custom/images/${filename}`, - }, - }, - }, - validate: function (value) { - return SettingsValidator.compose( - SettingsValidator.filePickerFileSize({ - ...this.options.file.size, - meaningfulUnit: true, - }), - SettingsValidator.filePickerSupportedExtensions( - this.options.file.extensions, - ), - )(value); - }, - }, - 'customization.reports.footer': { - title: 'Reports footer', - description: 'Set the footer of the reports.', - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.textarea, - defaultValue: '', - defaultValueIfNotSet: REPORTS_PAGE_FOOTER_TEXT, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { maxRows: 2, maxLength: 50 }, - validate: function (value) { - return SettingsValidator.multipleLinesString({ - maxRows: this.options?.maxRows, - maxLength: this.options?.maxLength, - })(value); - }, - validateBackend: function (schema) { - return schema.string({ validate: this.validate.bind(this) }); - }, - }, - 'customization.reports.header': { - title: 'Reports header', - description: 'Set the header of the reports.', - category: SettingCategory.CUSTOMIZATION, - type: EpluginSettingType.textarea, - defaultValue: '', - defaultValueIfNotSet: REPORTS_PAGE_HEADER_TEXT, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { maxRows: 3, maxLength: 40 }, - validate: function (value) { - return SettingsValidator.multipleLinesString({ - maxRows: this.options?.maxRows, - maxLength: this.options?.maxLength, - })(value); - }, - validateBackend: function (schema) { - return schema.string({ validate: this.validate.bind(this) }); - }, - }, - 'enrollment.dns': { - title: 'Enrollment DNS', - description: - 'Specifies the Wazuh registration server, used for the agent enrollment.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.text, - defaultValue: '', - isConfigurableFromFile: true, - isConfigurableFromUI: true, - validate: SettingsValidator.hasNoSpaces, - validateBackend: function (schema) { - return schema.string({ validate: this.validate }); - }, - }, - 'enrollment.password': { - title: 'Enrollment password', - description: - 'Specifies the password used to authenticate during the agent enrollment.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.text, - defaultValue: '', - isConfigurableFromFile: true, - isConfigurableFromUI: false, - validate: SettingsValidator.isNotEmptyString, - validateBackend: function (schema) { - return schema.string({ validate: this.validate }); - }, - }, - hideManagerAlerts: { - title: 'Hide manager alerts', - description: 'Hide the alerts of the manager in every dashboard.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.switch, - defaultValue: false, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresReloadingBrowserTab: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'ip.ignore': { - title: 'Index pattern ignore', - description: - 'Disable certain index pattern names from being available in index pattern selector.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.editor, - defaultValue: [], - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - editor: { - language: 'json', - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: any): any { - return JSON.stringify(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): any { - try { - return JSON.parse(value); - } catch (error) { - return value; - } - }, - // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: SettingsValidator.json( - SettingsValidator.compose( - SettingsValidator.array( - SettingsValidator.compose( - SettingsValidator.isString, - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noLiteralString('.', '..'), - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - ), - ), - ), - ), - ), - validateBackend: function (schema) { - return schema.arrayOf( - schema.string({ - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noLiteralString('.', '..'), - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - ), - ), - }), - ); - }, - }, - 'ip.selector': { - title: 'IP selector', - description: - 'Define if the user is allowed to change the selected index pattern directly from the top menu bar.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.switch, - defaultValue: true, - isConfigurableFromFile: true, - isConfigurableFromUI: false, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - pattern: { - title: 'Index pattern', - description: - "Default index pattern to use on the app. If there's no valid index pattern, the app will automatically create one with the name indicated in this option.", - category: SettingCategory.GENERAL, - type: EpluginSettingType.text, - defaultValue: WAZUH_ALERTS_PATTERN, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - // Validation: https://github.com/elastic/elasticsearch/blob/v7.10.2/docs/reference/indices/create-index.asciidoc - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noLiteralString('.', '..'), - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - ), - ), - validateBackend: function (schema) { - return schema.string({ validate: this.validate }); - }, - }, - timeout: { - title: 'Request timeout', - description: - 'Maximum time, in milliseconds, the app will wait for an API response when making requests to it. It will be ignored if the value is set under 1500 milliseconds.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.number, - defaultValue: 20000, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - options: { - number: { - min: 1500, - integer: true, - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: number) { - return String(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): number { - return Number(value); - }, - validate: function (value) { - return SettingsValidator.number(this.options.number)(value); - }, - validateBackend: function (schema) { - return schema.number({ validate: this.validate.bind(this) }); - }, - }, - 'wazuh.monitoring.creation': { - title: 'Index creation', - description: - 'Define the interval in which a new wazuh-monitoring index will be created.', - category: SettingCategory.MONITORING, - type: EpluginSettingType.select, - options: { - select: [ - { - text: 'Hourly', - value: 'h', - }, - { - text: 'Daily', - value: 'd', - }, - { - text: 'Weekly', - value: 'w', - }, - { - text: 'Monthly', - value: 'm', - }, - ], - }, - defaultValue: WAZUH_MONITORING_DEFAULT_CREATION, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - validate: function (value) { - return SettingsValidator.literal( - this.options.select.map(({ value }) => value), - )(value); - }, - validateBackend: function (schema) { - return schema.oneOf( - this.options.select.map(({ value }) => schema.literal(value)), - ); - }, - }, - 'wazuh.monitoring.enabled': { - title: 'Status', - description: - 'Enable or disable the wazuh-monitoring index creation and/or visualization.', - category: SettingCategory.MONITORING, - type: EpluginSettingType.switch, - defaultValue: WAZUH_MONITORING_DEFAULT_ENABLED, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: true, - options: { - switch: { - values: { - disabled: { label: 'false', value: false }, - enabled: { label: 'true', value: true }, - }, - }, - }, - uiFormTransformChangedInputValue: function ( - value: boolean | string, - ): boolean { - return Boolean(value); - }, - validate: SettingsValidator.isBoolean, - validateBackend: function (schema) { - return schema.boolean(); - }, - }, - 'wazuh.monitoring.frequency': { - title: 'Frequency', - description: - 'Frequency, in seconds, of API requests to get the state of the agents and create a new document in the wazuh-monitoring index with this data.', - category: SettingCategory.MONITORING, - type: EpluginSettingType.number, - defaultValue: WAZUH_MONITORING_DEFAULT_FREQUENCY, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: true, - options: { - number: { - min: 60, - integer: true, - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: number) { - return String(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): number { - return Number(value); - }, - validate: function (value) { - return SettingsValidator.number(this.options.number)(value); - }, - validateBackend: function (schema) { - return schema.number({ validate: this.validate.bind(this) }); - }, - }, - 'wazuh.monitoring.pattern': { - title: 'Index pattern', - description: 'Default index pattern to use for Wazuh monitoring.', - category: SettingCategory.MONITORING, - type: EpluginSettingType.text, - defaultValue: WAZUH_MONITORING_PATTERN, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noLiteralString('.', '..'), - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - ), - ), - validateBackend: function (schema) { - return schema.string({ minLength: 1, validate: this.validate }); - }, - }, - 'wazuh.monitoring.replicas': { - title: 'Index replicas', - description: - 'Define the number of replicas to use for the wazuh-monitoring-* indices.', - category: SettingCategory.MONITORING, - type: EpluginSettingType.number, - defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_REPLICAS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - options: { - number: { - min: 0, - integer: true, - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: number) { - return String(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): number { - return Number(value); - }, - validate: function (value) { - return SettingsValidator.number(this.options.number)(value); - }, - validateBackend: function (schema) { - return schema.number({ validate: this.validate.bind(this) }); - }, - }, - 'wazuh.monitoring.shards': { - title: 'Index shards', - description: - 'Define the number of shards to use for the wazuh-monitoring-* indices.', - category: SettingCategory.MONITORING, - type: EpluginSettingType.number, - defaultValue: WAZUH_MONITORING_DEFAULT_INDICES_SHARDS, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: true, - options: { - number: { - min: 1, - integer: true, - }, - }, - uiFormTransformConfigurationValueToInputValue: function (value: number) { - return String(value); - }, - uiFormTransformInputValueToConfigurationValue: function ( - value: string, - ): number { - return Number(value); - }, - validate: function (value) { - return SettingsValidator.number(this.options.number)(value); - }, - validateBackend: function (schema) { - return schema.number({ validate: this.validate.bind(this) }); - }, - }, - 'vulnerabilities.pattern': { - title: 'Index pattern', - description: 'Default index pattern to use for vulnerabilities.', - category: SettingCategory.VULNERABILITIES, - type: EpluginSettingType.text, - defaultValue: WAZUH_VULNERABILITIES_PATTERN, - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRunningHealthCheck: false, - validate: SettingsValidator.compose( - SettingsValidator.isNotEmptyString, - SettingsValidator.hasNoSpaces, - SettingsValidator.noLiteralString('.', '..'), - SettingsValidator.noStartsWithString('-', '_', '+', '.'), - SettingsValidator.hasNotInvalidCharacters( - '\\', - '/', - '?', - '"', - '<', - '>', - '|', - ',', - '#', - ), - ), - validateBackend: function (schema) { - return schema.string({ minLength: 1, validate: this.validate }); - }, - }, -}; - -export type TPluginSettingKey = keyof typeof PLUGIN_SETTINGS; - export enum HTTP_STATUS_CODES { CONTINUE = 100, SWITCHING_PROTOCOLS = 101, diff --git a/plugins/main/common/plugin.ts b/plugins/main/common/plugin.ts deleted file mode 100644 index edb5c76d0f..0000000000 --- a/plugins/main/common/plugin.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { PLUGIN_PLATFORM_BASE_INSTALLATION_PATH } from "./constants"; - -/** - * - * @param path Path to file or directory - * @returns Absolute path to the file or directory with the prefix path of app data path - */ -export const getPluginDataPath = (path: string = ''): string => `${PLUGIN_PLATFORM_BASE_INSTALLATION_PATH}${path}`; \ No newline at end of file diff --git a/plugins/main/common/services/settings-validator.ts b/plugins/main/common/services/settings-validator.ts deleted file mode 100644 index b62675f0f9..0000000000 --- a/plugins/main/common/services/settings-validator.ts +++ /dev/null @@ -1,235 +0,0 @@ -import path from 'path'; -import { formatBytes } from './file-size'; - -export class SettingsValidator { - /** - * Create a function that is a composition of the input validations - * @param functions SettingsValidator functions to compose - * @returns composed validation - */ - static compose(...functions) { - return function composedValidation(value) { - for (const fn of functions) { - const result = fn(value); - if (typeof result === 'string' && result.length > 0) { - return result; - }; - }; - }; - }; - - /** - * Check the value is a string - * @param value - * @returns - */ - static isString(value: unknown): string | undefined { - return typeof value === 'string' ? undefined : "Value is not a string."; - }; - - /** - * Check the string has no spaces - * @param value - * @returns - */ - static hasNoSpaces(value: string): string | undefined { - return /^\S*$/.test(value) ? undefined : "No whitespaces allowed."; - }; - - /** - * Check the string has no empty - * @param value - * @returns - */ - static isNotEmptyString(value: string): string | undefined { - if (typeof value === 'string') { - if (value.length === 0) { - return "Value can not be empty." - } else { - return undefined; - } - }; - }; - - /** - * Check the number of string lines is limited - * @param options - * @returns - */ - static multipleLinesString(options: { minRows?: number, maxRows?: number, maxLength?: number } = {}) { - return function (value: number) { - const lines = value.split(/\r\n|\r|\n/).length; - if (typeof options.maxLength !== 'undefined' && value.split('\n').some(line => line.length > options.maxLength)) { - return `The maximum length of a line is ${options.maxLength} characters.`; - }; - if (typeof options.minRows !== 'undefined' && lines < options.minRows) { - return `The string should have more or ${options.minRows} line/s.`; - }; - if (typeof options.maxRows !== 'undefined' && lines > options.maxRows) { - return `The string should have less or equal to ${options.maxRows} line/s.`; - }; - } - }; - - /** - * Creates a function that checks the string does not contain some characters - * @param invalidCharacters - * @returns - */ - static hasNotInvalidCharacters(...invalidCharacters: string[]) { - return function (value: string): string | undefined { - return invalidCharacters.some(invalidCharacter => value.includes(invalidCharacter)) - ? `It can't contain invalid characters: ${invalidCharacters.join(', ')}.` - : undefined; - }; - }; - - /** - * Creates a function that checks the string does not start with a substring - * @param invalidStartingCharacters - * @returns - */ - static noStartsWithString(...invalidStartingCharacters: string[]) { - return function (value: string): string | undefined { - return invalidStartingCharacters.some(invalidStartingCharacter => value.startsWith(invalidStartingCharacter)) - ? `It can't start with: ${invalidStartingCharacters.join(', ')}.` - : undefined; - }; - }; - - /** - * Creates a function that checks the string is not equals to some values - * @param invalidLiterals - * @returns - */ - static noLiteralString(...invalidLiterals: string[]) { - return function (value: string): string | undefined { - return invalidLiterals.some(invalidLiteral => value === invalidLiteral) - ? `It can't be: ${invalidLiterals.join(', ')}.` - : undefined; - }; - }; - - /** - * Check the value is a boolean - * @param value - * @returns - */ - static isBoolean(value: string): string | undefined { - return typeof value === 'boolean' - ? undefined - : "It should be a boolean. Allowed values: true or false."; - }; - - /** - * Check the value is a number between some optional limits - * @param options - * @returns - */ - static number(options: { min?: number, max?: number, integer?: boolean } = {}) { - return function (value: number) { - if (options.integer - && ( - (typeof value === 'string' ? ['.', ','].some(character => value.includes(character)) : false) - || !Number.isInteger(Number(value)) - ) - ) { - return 'Number should be an integer.' - }; - - const valueNumber = typeof value === 'string' ? Number(value) : value; - - if (typeof options.min !== 'undefined' && valueNumber < options.min) { - return `Value should be greater or equal than ${options.min}.`; - }; - if (typeof options.max !== 'undefined' && valueNumber > options.max) { - return `Value should be lower or equal than ${options.max}.`; - }; - }; - }; - - /** - * Creates a function that checks if the value is a json - * @param validateParsed Optional parameter to validate the parsed object - * @returns - */ - static json(validateParsed: (object: any) => string | undefined) { - return function (value: string) { - let jsonObject; - // Try to parse the string as JSON - try { - jsonObject = JSON.parse(value); - } catch (error) { - return "Value can't be parsed. There is some error."; - }; - - return validateParsed ? validateParsed(jsonObject) : undefined; - }; - }; - - /** - * Creates a function that checks is the value is an array and optionally validates each element - * @param validationElement Optional function to validate each element of the array - * @returns - */ - static array(validationElement: (json: any) => string | undefined) { - return function (value: unknown[]) { - // Check the JSON is an array - if (!Array.isArray(value)) { - return 'Value is not a valid list.'; - }; - - return validationElement - ? value.reduce((accum, elementValue) => { - if (accum) { - return accum; - }; - - const resultValidationElement = validationElement(elementValue); - if (resultValidationElement) { - return resultValidationElement; - }; - - return accum; - }, undefined) - : undefined; - }; - }; - - /** - * Creates a function that checks if the value is equal to list of values - * @param literals Array of values to compare - * @returns - */ - static literal(literals: unknown[]) { - return function (value: any): string | undefined { - return literals.includes(value) ? undefined : `Invalid value. Allowed values: ${literals.map(String).join(', ')}.`; - }; - }; - - // FilePicker - static filePickerSupportedExtensions = (extensions: string[]) => (options: { name: string }) => { - if (typeof options === 'undefined' || typeof options.name === 'undefined') { - return; - } - if (!extensions.includes(path.extname(options.name))) { - return `File extension is invalid. Allowed file extensions: ${extensions.join(', ')}.`; - }; - }; - - /** - * filePickerFileSize - * @param options - */ - static filePickerFileSize = (options: { maxBytes?: number, minBytes?: number, meaningfulUnit?: boolean }) => (value: { size: number }) => { - if (typeof value === 'undefined' || typeof value.size === 'undefined') { - return; - }; - if (typeof options.minBytes !== 'undefined' && value.size <= options.minBytes) { - return `File size should be greater or equal than ${options.meaningfulUnit ? formatBytes(options.minBytes) : `${options.minBytes} bytes`}.`; - }; - if (typeof options.maxBytes !== 'undefined' && value.size >= options.maxBytes) { - return `File size should be lower or equal than ${options.meaningfulUnit ? formatBytes(options.maxBytes) : `${options.maxBytes} bytes`}.`; - }; - }; -}; diff --git a/plugins/main/common/services/settings.test.ts b/plugins/main/common/services/settings.test.ts deleted file mode 100644 index 21efe9e414..0000000000 --- a/plugins/main/common/services/settings.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - formatLabelValuePair, - formatSettingValueToFile, - getCustomizationSetting, -} from './settings'; - -describe('[settings] Methods', () => { - describe('formatLabelValuePair: Format the label-value pairs used to display the allowed values', () => { - it.each` - label | value | expected - ${'TestLabel'} | ${true} | ${'true (TestLabel)'} - ${'true'} | ${true} | ${'true'} - `( - `label: $label | value: $value | expected: $expected`, - ({ label, expected, value }) => { - expect(formatLabelValuePair(label, value)).toBe(expected); - }, - ); - }); - - describe('formatSettingValueToFile: Format setting values to save in the configuration file', () => { - it.each` - input | expected - ${'test'} | ${'"test"'} - ${'test space'} | ${'"test space"'} - ${'test\nnew line'} | ${'"test\\nnew line"'} - ${''} | ${'""'} - ${1} | ${1} - ${true} | ${true} - ${false} | ${false} - ${['test1']} | ${'["test1"]'} - ${['test1', 'test2']} | ${'["test1","test2"]'} - `(`input: $input | expected: $expected`, ({ input, expected }) => { - expect(formatSettingValueToFile(input)).toBe(expected); - }); - }); - - describe('getCustomizationSetting: Get the value for the "customization." settings depending on the "customization.enabled" setting', () => { - it.each` - customizationEnabled | settingKey | configValue | expected - ${true} | ${'customization.logo.app'} | ${'custom-image-app.png'} | ${'custom-image-app.png'} - ${true} | ${'customization.logo.app'} | ${''} | ${''} - ${false} | ${'customization.logo.app'} | ${'custom-image-app.png'} | ${''} - ${false} | ${'customization.logo.app'} | ${''} | ${''} - ${true} | ${'customization.reports.footer'} | ${'Custom footer'} | ${'Custom footer'} - ${true} | ${'customization.reports.footer'} | ${''} | ${'Copyright © 2023 Wazuh, Inc.'} - ${false} | ${'customization.reports.footer'} | ${'Custom footer'} | ${'Copyright © 2023 Wazuh, Inc.'} - ${false} | ${'customization.reports.footer'} | ${''} | ${'Copyright © 2023 Wazuh, Inc.'} - ${false} | ${'customization.reports.footer'} | ${''} | ${'Copyright © 2023 Wazuh, Inc.'} - ${true} | ${'customization.reports.header'} | ${'Custom header'} | ${'Custom header'} - ${true} | ${'customization.reports.header'} | ${''} | ${'info@wazuh.com\nhttps://wazuh.com'} - ${false} | ${'customization.reports.header'} | ${'Custom header'} | ${'info@wazuh.com\nhttps://wazuh.com'} - ${false} | ${'customization.reports.header'} | ${''} | ${'info@wazuh.com\nhttps://wazuh.com'} - `( - `customizationEnabled: $customizationEnabled | settingKey: $settingKey | configValue: $configValue | expected: $expected`, - ({ configValue, customizationEnabled, expected, settingKey }) => { - const configuration = { - 'customization.enabled': customizationEnabled, - [settingKey]: configValue, - }; - expect(getCustomizationSetting(configuration, settingKey)).toBe( - expected, - ); - }, - ); - }); -}); diff --git a/plugins/main/common/services/settings.ts b/plugins/main/common/services/settings.ts deleted file mode 100644 index 868f54c984..0000000000 --- a/plugins/main/common/services/settings.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { - PLUGIN_SETTINGS, - PLUGIN_SETTINGS_CATEGORIES, - TPluginSetting, - TPluginSettingKey, - TPluginSettingWithKey -} from '../constants'; -import { formatBytes } from './file-size'; - -/** - * Look for a configuration category setting by its name - * @param categoryTitle - * @returns category settings - */ -export function getCategorySettingByTitle(categoryTitle: string): any { - return Object.entries(PLUGIN_SETTINGS_CATEGORIES).find(([key, category]) => category?.title == categoryTitle)?.[1]; -} - -/** - * Get the default value of the plugin setting. - * @param setting setting key - * @returns setting default value. It returns `defaultValueIfNotSet` or `defaultValue`. - */ -export function getSettingDefaultValue(settingKey: string): any { - return typeof PLUGIN_SETTINGS[settingKey].defaultValueIfNotSet !== 'undefined' - ? PLUGIN_SETTINGS[settingKey].defaultValueIfNotSet - : PLUGIN_SETTINGS[settingKey].defaultValue; -}; - -/** - * Get the default settings configuration. key-value pair - * @returns an object with key-value pairs whose value is the default one - */ -export function getSettingsDefault() : {[key in TPluginSettingKey]: unknown} { - return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ({ - ...accum, - [pluginSettingID]: pluginSettingConfiguration.defaultValue - }), {}); -}; - -/** - * Get the settings grouped by category - * @returns an object whose keys are the categories and its value is an array of setting of that category - */ -export function getSettingsByCategories() : {[key: string]: TPluginSetting[]} { - return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ({ - ...accum, - [pluginSettingConfiguration.category]: [...(accum[pluginSettingConfiguration.category] || []), { ...pluginSettingConfiguration, key: pluginSettingID }] - }), {}); -}; - -/** - * Get the plugin settings as an array - * @returns an array of plugin setting denifitions including the key - */ -export function getSettingsDefaultList(): TPluginSettingWithKey[] { - return Object.entries(PLUGIN_SETTINGS).reduce((accum, [pluginSettingID, pluginSettingConfiguration]) => ([ - ...accum, - { ...pluginSettingConfiguration, key: pluginSettingID } - ]), []); -}; - -/** - * Format the plugin setting value received in the backend to store in the plugin configuration file (.yml). - * @param value plugin setting value sent to the endpoint - * @returns valid value to .yml - */ -export function formatSettingValueToFile(value: any) { - const formatter = formatSettingValueToFileType[typeof value] || formatSettingValueToFileType.default; - return formatter(value); -}; - -const formatSettingValueToFileType = { - string: (value: string): string => `"${value.replace(/"/,'\\"').replace(/\n/g,'\\n')}"`, // Escape the " character and new line - object: (value: any): string => JSON.stringify(value), - default: (value: any): any => value -}; - -/** - * Group the settings by category - * @param settings - * @returns - */ -export function groupSettingsByCategory(settings: TPluginSettingWithKey[]){ - const settingsSortedByCategories = settings - .sort((settingA, settingB) => settingA.key?.localeCompare?.(settingB.key)) - .reduce((accum, pluginSettingConfiguration) => ({ - ...accum, - [pluginSettingConfiguration.category]: [ - ...(accum[pluginSettingConfiguration.category] || []), - { ...pluginSettingConfiguration } - ] - }), {}); - - return Object.entries(settingsSortedByCategories) - .map(([category, settings]) => ({ category, settings })) - .filter(categoryEntry => categoryEntry.settings.length); -}; - -/** - * Get the plugin setting description composed. - * @param options - * @returns - */ - export function getPluginSettingDescription({description, options}: TPluginSetting): string{ - return [ - description, - ...(options?.select ? [`Allowed values: ${options.select.map(({text, value}) => formatLabelValuePair(text, value)).join(', ')}.`] : []), - ...(options?.switch ? [`Allowed values: ${['enabled', 'disabled'].map(s => formatLabelValuePair(options.switch.values[s].label, options.switch.values[s].value)).join(', ')}.`] : []), - ...(options?.number && 'min' in options.number ? [`Minimum value: ${options.number.min}.`] : []), - ...(options?.number && 'max' in options.number ? [`Maximum value: ${options.number.max}.`] : []), - // File extensions - ...(options?.file?.extensions ? [`Supported extensions: ${options.file.extensions.join(', ')}.`] : []), - // File recommended dimensions - ...(options?.file?.recommended?.dimensions ? [`Recommended dimensions: ${options.file.recommended.dimensions.width}x${options.file.recommended.dimensions.height}${options.file.recommended.dimensions.unit || ''}.`] : []), - // File size - ...((options?.file?.size && typeof options.file.size.minBytes !== 'undefined') ? [`Minimum file size: ${formatBytes(options.file.size.minBytes)}.`] : []), - ...((options?.file?.size && typeof options.file.size.maxBytes !== 'undefined') ? [`Maximum file size: ${formatBytes(options.file.size.maxBytes)}.`] : []), - // Multi line text - ...((options?.maxRows && typeof options.maxRows !== 'undefined' ? [`Maximum amount of lines: ${options.maxRows}.`] : [])), - ...((options?.minRows && typeof options.minRows !== 'undefined' ? [`Minimum amount of lines: ${options.minRows}.`] : [])), - ...((options?.maxLength && typeof options.maxLength !== 'undefined' ? [`Maximum lines length is ${options.maxLength} characters.`] : [])), - ].join(' '); -}; - -/** - * Format the pair value-label to display the pair. If label and the string of value are equals, only displays the value, if not, displays both. - * @param value - * @param label - * @returns - */ -export function formatLabelValuePair(label, value){ - return label !== `${value}` - ? `${value} (${label})` - : `${value}` -}; - -/** - * Get the configuration value if the customization is enabled. - * @param configuration JSON object from `wazuh.yml` - * @param settingKey key of the setting - * @returns - */ -export function getCustomizationSetting(configuration: {[key: string]: any }, settingKey: string): any { - const isCustomizationEnabled = typeof configuration['customization.enabled'] === 'undefined' - ? getSettingDefaultValue('customization.enabled') - : configuration['customization.enabled']; - const defaultValue = getSettingDefaultValue(settingKey); - - if ( isCustomizationEnabled && settingKey.startsWith('customization') && settingKey !== 'customization.enabled'){ - return (typeof configuration[settingKey] !== 'undefined' ? resolveEmptySetting(settingKey, configuration[settingKey]) : defaultValue); - }else{ - return defaultValue; - }; -}; - -/** - * Returns the default value if not set when the setting is an empty string - * @param settingKey plugin setting - * @param value value of the plugin setting - * @returns - */ -function resolveEmptySetting(settingKey: string, value : unknown){ - return typeof value === 'string' && value.length === 0 && PLUGIN_SETTINGS[settingKey].defaultValueIfNotSet - ? getSettingDefaultValue(settingKey) - : value; -}; diff --git a/plugins/main/opensearch_dashboards.json b/plugins/main/opensearch_dashboards.json index d2beb8d5a5..7af70c4235 100644 --- a/plugins/main/opensearch_dashboards.json +++ b/plugins/main/opensearch_dashboards.json @@ -18,8 +18,7 @@ "opensearchDashboardsUtils", "opensearchDashboardsLegacy", "wazuhCheckUpdates", - "wazuhCore", - "wazuhEndpoints" + "wazuhCore" ], "optionalPlugins": [ "security", diff --git a/plugins/main/package.json b/plugins/main/package.json index 9f1cc623d6..1a003dc499 100644 --- a/plugins/main/package.json +++ b/plugins/main/package.json @@ -3,7 +3,7 @@ "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.11.0" + "version": "2.12.0" }, "description": "Wazuh dashboard", "keywords": [ diff --git a/plugins/main/public/app.js b/plugins/main/public/app.js index f8e23658ea..bca52fa9ec 100644 --- a/plugins/main/public/app.js +++ b/plugins/main/public/app.js @@ -42,12 +42,14 @@ import './controllers'; import './factories'; // Imports to update currentPlatform when app starts -import { checkCurrentSecurityPlatform } from './controllers/management/components/management/configuration/utils/wz-fetch'; import store from './redux/store'; -import { updateCurrentPlatform } from './redux/actions/appStateActions'; +import { + updateCurrentPlatform, + updateUserAccount, +} from './redux/actions/appStateActions'; import { WzAuthentication, loadAppConfig } from './react-services'; -import { getAngularModule, getHttp } from './kibana-services'; +import { getAngularModule, getWazuhCorePlugin } from './kibana-services'; const app = getAngularModule(); @@ -73,11 +75,20 @@ app.run([ app.$injector = _$injector; // Set currentSecurity platform in Redux when app starts. - checkCurrentSecurityPlatform() + getWazuhCorePlugin() + .dashboardSecurity.fetchCurrentPlatform() .then(item => { store.dispatch(updateCurrentPlatform(item)); }) - .catch(() => { }); + .catch(() => {}); + + // Set user account data in Redux when app starts. + getWazuhCorePlugin() + .dashboardSecurity.fetchAccount() + .then(response => { + store.dispatch(updateUserAccount(response)); + }) + .catch(() => {}); // Init the process of refreshing the user's token when app start. checkPluginVersion().finally(WzAuthentication.refresh); @@ -101,7 +112,6 @@ app.run(function ($rootElement) { `); - // Bind deleteExistentToken on Log out component. $('.euiHeaderSectionItem__button, .euiHeaderSectionItemButton').on( 'mouseleave', diff --git a/plugins/main/public/components/add-modules-data/WzSampleDataWrapper.js b/plugins/main/public/components/add-modules-data/WzSampleDataWrapper.js index 3bf1213a52..c7a0e79e52 100644 --- a/plugins/main/public/components/add-modules-data/WzSampleDataWrapper.js +++ b/plugins/main/public/components/add-modules-data/WzSampleDataWrapper.js @@ -29,7 +29,6 @@ import { withReduxProvider, } from '../../components/common/hocs'; import { compose } from 'redux'; -import { WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../common/constants'; export class WzSampleDataProvider extends Component { constructor(props) { @@ -68,5 +67,5 @@ export class WzSampleDataProvider extends Component { export const WzSampleDataWrapper = compose( withErrorBoundary, withReduxProvider, - withUserAuthorizationPrompt(null, [WAZUH_ROLE_ADMINISTRATOR_NAME]), + withUserAuthorizationPrompt(null, { isAdmininistrator: true }), )(WzSampleDataProvider); diff --git a/plugins/main/public/components/add-modules-data/sample-data.tsx b/plugins/main/public/components/add-modules-data/sample-data.tsx index 80989e10e1..ee7ab442ca 100644 --- a/plugins/main/public/components/add-modules-data/sample-data.tsx +++ b/plugins/main/public/components/add-modules-data/sample-data.tsx @@ -10,7 +10,7 @@ * Find more information about this on the LICENSE file. */ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import { WzButtonPermissions } from '../../components/common/permissions/button'; import { @@ -25,8 +25,6 @@ import { import { getToasts } from '../../kibana-services'; import { WzRequest } from '../../react-services/wz-request'; import { AppState } from '../../react-services/app-state'; -import { WAZUH_ROLE_ADMINISTRATOR_NAME } from '../../../common/constants'; - import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; import { UI_LOGGER_LEVELS } from '../../../common/constants'; import { getErrorOrchestrator } from '../../react-services/common-services'; @@ -286,7 +284,7 @@ export default class WzSampleData extends Component { {(exists && ( this.removeSampleData(category)} > {(removeDataLoading && 'Removing data') || 'Remove data'} @@ -294,7 +292,7 @@ export default class WzSampleData extends Component { )) || ( this.addSampleData(category)} > {(addDataLoading && 'Adding data') || 'Add data'} diff --git a/plugins/main/public/components/agents/__snapshots__/agent-status.test.tsx.snap b/plugins/main/public/components/agents/__snapshots__/agent-status.test.tsx.snap index 6253e66ffd..e4b3c3708c 100644 --- a/plugins/main/public/components/agents/__snapshots__/agent-status.test.tsx.snap +++ b/plugins/main/public/components/agents/__snapshots__/agent-status.test.tsx.snap @@ -26,7 +26,11 @@ exports[`AgentStatus component Renders status indicator with the its color and t viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
+ > + +
`; diff --git a/plugins/main/public/components/agents/__snapshots__/agent-synced.test.tsx.snap b/plugins/main/public/components/agents/__snapshots__/agent-synced.test.tsx.snap index de773fa3b7..21088099b7 100644 --- a/plugins/main/public/components/agents/__snapshots__/agent-synced.test.tsx.snap +++ b/plugins/main/public/components/agents/__snapshots__/agent-synced.test.tsx.snap @@ -23,7 +23,11 @@ Array [ viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
+ > + +
- - + > + + + Start: @@ -459,7 +463,7 @@ exports[`AgentStatTable component Renders correctly to match the snapshot 1`] = size="m" type="importAction" > - - + > + + +
-
-

- Network interfaces - - (0) - -

-
- + Network interfaces + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
- -
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + - - - - + + + + + + +
+
+ + No items found + +
+
- + + + +
+
+
+
+
-
- - - - - - - - - - + + + + +
+
+
+
-
- - -
-
+
+
+

+ Network ports + + (0) + +

+
+
+ +
+
+
-
- - - - +
-
- -
-
- +
- No items found - +
+ +
+
+
+ +
+
+
-
-
-
-
-
-
-
-
-
-

- Network ports - - (0) - -

-
-
-
-
- -
-
- +
+
-
-
-
-
+
-
-
+
+
- -
+ class="euiFlexItem euiFlexItem--flexGrowZero" + />
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + +
+
+ + + Local port + + + + + + Local IP address + + + - - - - - - - - -
-
-
-
-
-
-
-
-
-
+
- - - - +
+
+ + No items found + +
+
- - - - - - - - - - - - - - -
-
- - - Local port - - - - - - Local IP address - - - - - - -
-
- - No items found - -
-
@@ -845,377 +885,397 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Network settings - - (0) - -

-
- + Network settings + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
- +
+
+
+
+
+
+ + + + + + +
+
+ + + - - - - - - - - - - - - + - + - + + + + + - - - - - - -
-
- - - - - Address - - - - - + - - Netmask - - - - - + - - Protocol - - - - - +
+
- Broadcast - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -1231,185 +1291,262 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Packages - - (0) - -

-
- + Packages + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - - + - + - + + + + + - - - - - - -
-
- - - - - Version - - - - - + - - Format - - - - - + - - Location - - - - - +
+
- Description - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -1619,185 +1699,263 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Processes - - (0) - -

-
- + Processes + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - + - - + - + - + - + + + + + - - - - - - -
-
- - - Name - - - - - - + - - Effective user - - - - - - - + - - Parent PID - - - - - + - - VM size - - - - - + - - Priority - - - - - +
+
- State - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -2177,360 +2277,396 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Network interfaces - - (0) - -

-
- + Network interfaces + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - + - - + - + + + + + - - - - - - -
-
- - - Name - - - - - - + - - MAC - - - - - - - + - - MTU - - - - - +
+
- Type - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -2543,1084 +2679,780 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Network ports - - (0) - -

-
- + Network ports + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
- + + + + Sorting + + + +
+
-
-
- - - - - - + + - + - + - - - - - - - -
-
- - - Local port - - - - - - Local IP address - - - - - +
+
- Process - - - - - + + Local port + + + - PID - - - - - State + + Process + - - - - - Protocol + + PID + - - -
-
- +
- No items found - - - -
-
-
-
-
-
-
-
-
-
-
-

- Network settings - - (0) - -

-
-
-
-
- -
-
- -
-
-
-
-
-
-
-
-
-
-
- -
-
-
+ + + State + + + + + -
-
-
-
+ + + + + + +
+ + No items found + +
+ + + +
+
+
+
+
+
+
-
-
+
-
-
-
- -
-
+ Network settings + + (0) + +
-
- - - - - - - - - - - - - - - -
-
+
+
-
- - - - +
-
- -
-
- - No items found - -
-
-
-
-
-
-
-
-
-
-
-
-

- Packages - - (0) - -

-
-
-
-
- -
-
- +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - - + - + - + + + + + - - - - - - -
-
- - - - - Architecture - - - - - + - - Version - - - - - + - - Vendor - - - - - +
+
- Description - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -3636,532 +3468,976 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Processes - - (0) - -

-
- + Packages + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + - - - - - + + + + + + + +
+
- - - - - - - - - - - + - + - + - + + + + + + + +
-
- - - Name - - - - - - + - - Effective user - - - - - + - - Effective group - - - - - + - - PID - - - - + + Description + + + +
+
+ + No items found + +
+
+ + + + + + + +
+
+
+
+
+
+
+
+
- -
+ + + +
+
+
-
+
-
+ + + + +
+
+
+
+
- -
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
- -
- + + + + + + + + + + - + - + + + + + + + + + - - - - - - -
+
- - Session - - - - - + - - Priority - - - - - + + + + + + + + + + + + + + + + + - - State - - - -
-
- + + Priority + + + + +
- No items found - - - -
+ +
+
+ + No items found + +
+
+
+
@@ -4299,185 +4575,262 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Network interfaces - - (0) - -

-
- + Network interfaces + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - - + - + - + + + + + - - - - - - -
-
- - - - - MAC - - - - - + - - State - - - - - + - - MTU - - - - - +
+
- Type - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -4682,347 +4978,367 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Network ports - - (0) - -

-
- + Network ports + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
- + + + + Sorting + + + +
+
-
-
- - - - - - + + - - - - - - - - -
-
- - - Local port - - - - - - Local IP address - - - -
+
- Process + + Local port + - - - - - State + + Local IP address + - - - - - - Protocol - - - -
-
- + + Process + + + + +
- No items found - - - -
+ + + + + + + + + + +
+ + No items found + +
+ + + + +
+
@@ -5039,185 +5355,262 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Network settings - - (0) - -

-
- + Network settings + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - - + - + - + + + + + - - - - - - -
-
- - - - - Address - - - - - + - - Netmask - - - - - + - - Protocol - - - - - +
+
- Broadcast - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -5422,185 +5758,262 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Windows updates - - (0) - -

-
- + Windows updates + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + +
+
- - - - +
+
+ + No items found + +
+
- - - - - - - - - - - -
-
- -
-
- - No items found - -
-
@@ -5712,185 +6068,262 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Packages - - (0) - -

-
- + Packages + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - - + - + + + + + - - - - - - -
-
- - - - - Architecture - - - - - + - - Version - - - - - +
+
- Vendor - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -6076,185 +6452,263 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow wz-agent-inventory-panel" >
-
-

- Processes - - (0) - -

-
- + Processes + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - - + - + - + - + - + + + + + - - - - - - -
-
- - - - - PID - - - - - + - - Parent PID - - - - - + - - VM size - - - - - + - - Priority - - - - - + - - NLWP - - - - - +
+
- Command - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
diff --git a/plugins/main/public/components/common/buttons/flyout.tsx b/plugins/main/public/components/common/buttons/flyout.tsx new file mode 100644 index 0000000000..7e22970bda --- /dev/null +++ b/plugins/main/public/components/common/buttons/flyout.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; +import { + WzButtonOpenOnClick, + WzButtonPermissionsOpenOnClick, +} from './modal-confirm'; +import { WzFlyout } from '../flyouts'; +import { + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiConfirmModal, + EuiOverlayMask, +} from '@elastic/eui'; + +function RenderFlyout({ flyoutTitle, flyoutProps, flyoutBody, onClose }) { + const [canClose, setCanClose] = useState(true); + const [canNotCloseIsOpen, setCanNotCloseIsOpen] = useState(false); + const onFlyoutClose = function () { + if (!canClose) { + setCanNotCloseIsOpen(true); + return; + } + onClose(); + }; + + return ( + <> + + + +

{flyoutTitle}

+
+
+ + {typeof flyoutBody === 'function' + ? flyoutBody({ + onClose, + onUpdateCanClose: setCanClose, + }) + : flyoutBody} + +
+ {canNotCloseIsOpen && ( + + setCanNotCloseIsOpen(false)} + cancelButtonText="No, don't do it" + confirmButtonText='Yes, do it' + > +

+ There are unsaved changes. Are you sure you want to proceed? +

+
+
+ )} + + ); +} + +export const WzButtonOpenFlyout: React.FunctionComponent = ({ + flyoutTitle, + flyoutProps = {}, + flyoutBody = null, + buttonProps = {}, + ...rest +}) => ( + ( + + )} + /> +); + +export const WzButtonPermissionsOpenFlyout: React.FunctionComponent = ({ + flyoutTitle, + flyoutProps = {}, + flyoutBody = null, + buttonProps = {}, + ...rest +}) => ( + ( + + )} + /> +); diff --git a/plugins/main/public/components/common/buttons/index.ts b/plugins/main/public/components/common/buttons/index.ts index 5aef0f94e0..da7fa9034f 100644 --- a/plugins/main/public/components/common/buttons/index.ts +++ b/plugins/main/public/components/common/buttons/index.ts @@ -11,4 +11,8 @@ */ export { WzButton } from './button'; -export { WzButtonModalConfirm, WzButtonPermissionsModalConfirm } from './modal-confirm'; \ No newline at end of file +export { + WzButtonModalConfirm, + WzButtonPermissionsModalConfirm, +} from './modal-confirm'; +export * from './flyout'; diff --git a/plugins/main/public/components/common/charts/visualizations/basic.tsx b/plugins/main/public/components/common/charts/visualizations/basic.tsx index 3897ae1788..2e48e5e734 100644 --- a/plugins/main/public/components/common/charts/visualizations/basic.tsx +++ b/plugins/main/public/components/common/charts/visualizations/basic.tsx @@ -1,23 +1,32 @@ -import React, { useCallback, useState } from "react"; -import { ChartLegend } from "./legend"; +import React, { useCallback, useState } from 'react'; +import { ChartLegend } from './legend'; import { ChartDonut, ChartDonutProps } from '../charts/donut'; -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingChart, EuiText, EuiSelect, EuiSpacer } from '@elastic/eui'; -import { useAsyncActionRunOnStart } from "../../hooks"; +import { + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingChart, + EuiText, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; +import { useAsyncActionRunOnStart } from '../../hooks'; +import './visualizations.scss'; export type VisualizationBasicProps = ChartDonutProps & { - type: 'donut', - size: number | string | { width: number | string, height: number | string } - showLegend?: boolean - isLoading?: boolean - noDataTitle?: string - noDataMessage?: string | (() => React.node) - errorTitle?: string - errorMessage?: string | (() => React.node) - error?: { message: string } -} + type: 'donut'; + size: number | string | { width: number | string; height: number | string }; + showLegend?: boolean; + isLoading?: boolean; + noDataTitle?: string; + noDataMessage?: string | (() => React.node); + errorTitle?: string; + errorMessage?: string | (() => React.node); + error?: { message: string }; +}; const chartTypes = { - 'donut': ChartDonut + donut: ChartDonut, }; /** @@ -34,131 +43,165 @@ export const VisualizationBasic = ({ noDataMessage, errorTitle = 'Error', errorMessage, - error + error, }: VisualizationBasicProps) => { - const { width, height } = typeof size === 'object' ? size : { width: size, height: size }; + const { width, height } = + typeof size === 'object' ? size : { width: size, height: size }; let visualization = null; if (isLoading) { visualization = ( -
- +
+
- ) + ); } else if (errorMessage || error?.message) { visualization = ( {errorTitle}} body={errorMessage || error?.message} /> - ) + ); } else if (!data || (Array.isArray(data) && !data.length)) { visualization = ( {noDataTitle}} - body={typeof noDataMessage === 'function' ? noDataMessage() : noDataMessage} + body={ + typeof noDataMessage === 'function' ? noDataMessage() : noDataMessage + } /> - ) + ); } else { const Chart = chartTypes[type]; - const chartFlexStyle = { - alignItems: 'flex-end', - paddingRight: '1em' - } - const legendFlexStyle = { - height: '100%', - paddingLeft: '1em' - } visualization = ( - - + + {showLegend && ( - + ({ ...rest, labelColor: color, color: 'text' }))} + data={data.map(({ color, ...rest }) => ({ + ...rest, + labelColor: color, + color: 'text', + }))} /> )} - ) + ); } return ( -
+
{visualization}
- ) - -} + ); +}; type VisualizationBasicWidgetProps = VisualizationBasicProps & { - onFetch: (...dependencies) => any[] - onFetchDependencies?: any[] -} + onFetch: (...dependencies) => any[]; + onFetchDependencies?: any[]; +}; /** * Component that fetch the data and renders the visualization using the visualization basic component */ -export const VisualizationBasicWidget = ({ onFetch, onFetchDependencies, ...rest }: VisualizationBasicWidgetProps) => { - const { running, ...restAsyncAction } = useAsyncActionRunOnStart(onFetch, onFetchDependencies); +export const VisualizationBasicWidget = ({ + onFetch, + onFetchDependencies, + ...rest +}: VisualizationBasicWidgetProps) => { + const { running, ...restAsyncAction } = useAsyncActionRunOnStart( + onFetch, + onFetchDependencies, + ); - return -} + return ( + + ); +}; type VisualizationBasicWidgetSelectorProps = VisualizationBasicWidgetProps & { - selectorOptions: { value: any, text: string }[] - title?: string - onFetchExtraDependencies?: any[] -} + selectorOptions: { value: any; text: string }[]; + title?: string; + onFetchExtraDependencies?: any[]; +}; /** - * Renders a visualization that has a selector to change the resource to fetch data and display it. Use the visualization basic. + * Renders a visualization that has a selector to change the resource to fetch data and display it. Use the visualization basic. */ -export const VisualizationBasicWidgetSelector = ({ selectorOptions, title, onFetchExtraDependencies, ...rest }: VisualizationBasicWidgetSelectorProps) => { - const [selectedOption, setSelectedOption] = useState(selectorOptions[0].value); +export const VisualizationBasicWidgetSelector = ({ + selectorOptions, + title, + onFetchExtraDependencies, + ...rest +}: VisualizationBasicWidgetSelectorProps) => { + const [selectedOption, setSelectedOption] = useState( + selectorOptions[0].value, + ); - const onChange = useCallback((e) => setSelectedOption(e.target.value)); + const onChange = useCallback(e => setSelectedOption(e.target.value)); return ( <> - + {title && ( -

-

{title}

+

+ +

{title}

+

)}
-
option.value === selectedOption)) - : rest.noDataMessage - } - : {} - )} - onFetchDependencies={[selectedOption, ...(onFetchExtraDependencies || [])]} + {...(rest.noDataMessage + ? { + noDataMessage: + typeof rest.noDataMessage === 'function' + ? rest.noDataMessage( + selectedOption, + selectorOptions.find( + option => option.value === selectedOption, + ), + ) + : rest.noDataMessage, + } + : {})} + onFetchDependencies={[ + selectedOption, + ...(onFetchExtraDependencies || []), + ]} /> - ) -} \ No newline at end of file + ); +}; diff --git a/plugins/main/public/components/common/charts/visualizations/legend.scss b/plugins/main/public/components/common/charts/visualizations/legend.scss index 99e97df2f2..a3a9b27787 100644 --- a/plugins/main/public/components/common/charts/visualizations/legend.scss +++ b/plugins/main/public/components/common/charts/visualizations/legend.scss @@ -1,7 +1,20 @@ +.chart-legend { + width: 60vw; + overflow: auto; + scrollbar-width: none; + @media (min-width: 1024px) { + width: 160px; + } +} + .chart-legend > li { - margin-top: 0px; + margin-top: 5px !important; +} + +.chart-legend > li svg { + margin-right: 6px; } .chart-legend > li > * { - padding: 0px; + padding: 0px; } diff --git a/plugins/main/public/components/common/charts/visualizations/legend.tsx b/plugins/main/public/components/common/charts/visualizations/legend.tsx index b36270bdb3..f3a2c6a6e8 100644 --- a/plugins/main/public/components/common/charts/visualizations/legend.tsx +++ b/plugins/main/public/components/common/charts/visualizations/legend.tsx @@ -1,32 +1,41 @@ -import React from "react"; -import { EuiIcon } from "@elastic/eui"; -import { EuiListGroup } from "@elastic/eui"; +import React from 'react'; +import { EuiIcon } from '@elastic/eui'; +import { EuiListGroup } from '@elastic/eui'; import './legend.scss'; type ChartLegendProps = { data: { - label: string - value: any - color: string - labelColor: string - }[] -} + label: string; + value: any; + color: string; + labelColor: string; + }[]; +}; /** * Create the legend to use with charts in visualizations. */ export function ChartLegend({ data }: ChartLegendProps) { - const list = data.map(({label, labelColor, value, ...rest}, idx) => ({ - label:
{`${label} (${value})`}
, - icon: , - ...rest + const list = data.map(({ label, labelColor, value, ...rest }, idx) => ({ + label: ( +
{`${label} (${value})`}
+ ), + icon: , + ...rest, })); return ( + flush + /> ); -} \ No newline at end of file +} diff --git a/plugins/main/public/components/common/charts/visualizations/visualizations.scss b/plugins/main/public/components/common/charts/visualizations/visualizations.scss new file mode 100644 index 0000000000..e24c29c81d --- /dev/null +++ b/plugins/main/public/components/common/charts/visualizations/visualizations.scss @@ -0,0 +1,19 @@ +.wazuh-visualization-layout { + @media (max-width: 767px) { + height: auto !important; + } +} + +.wazuh-visualization-chart { + align-items: flex-end; + padding-right: 1em; + min-height: 150px; + @media (max-width: 767px) { + align-items: center; + } +} + +.wazuh-visualization-legend { + height: 100%; + padding-left: 1em; +} diff --git a/plugins/main/public/components/common/error-boundary/__snapshots__/error-boundary.test.tsx.snap b/plugins/main/public/components/common/error-boundary/__snapshots__/error-boundary.test.tsx.snap index 9a0cb7b29a..b50866b573 100644 --- a/plugins/main/public/components/common/error-boundary/__snapshots__/error-boundary.test.tsx.snap +++ b/plugins/main/public/components/common/error-boundary/__snapshots__/error-boundary.test.tsx.snap @@ -57,7 +57,7 @@ exports[`ErrorBoundary component renders correctly to match the snapshot 1`] = ` size="xxl" type="faceSad" > - - + > + + + + > + +
@@ -357,7 +361,11 @@ exports[`[component] InputForm Renders correctly to match the snapshot: Input: s viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
@@ -396,7 +404,11 @@ exports[`[component] InputForm Renders correctly to match the snapshot: Input: s viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + + > + + diff --git a/plugins/main/public/components/common/form/hooks.test.tsx b/plugins/main/public/components/common/form/hooks.test.tsx index 283c4809bf..e0d8eee60c 100644 --- a/plugins/main/public/components/common/form/hooks.test.tsx +++ b/plugins/main/public/components/common/form/hooks.test.tsx @@ -2,9 +2,257 @@ import { fireEvent, render } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { renderHook, act } from '@testing-library/react-hooks'; import React, { useState } from 'react'; -import { useForm } from './hooks'; +import { + enhanceFormFields, + getFormFields, + mapFormFields, + useForm, +} from './hooks'; import { FormConfiguration, IInputForm } from './types'; +function inspect(obj) { + return console.log( + require('util').inspect(obj, false, null, true /* enable colors */), + ); +} + +describe('[hook] useForm utils', () => { + it('[utils] getFormFields', () => { + const result = getFormFields({ + text1: { + type: 'text', + initialValue: '', + }, + }); + expect(result.text1.currentValue).toBe(''); + expect(result.text1.initialValue).toBe(''); + }); + it('[utils] getFormFields', () => { + const result = getFormFields({ + text1: { + type: 'text', + initialValue: 'text1', + }, + number1: { + type: 'number', + initialValue: 1, + }, + }); + expect(result.text1.currentValue).toBe('text1'); + expect(result.text1.initialValue).toBe('text1'); + expect(result.number1.currentValue).toBe(1); + expect(result.number1.initialValue).toBe(1); + }); + it('[utils] getFormFields', () => { + const result = getFormFields({ + text1: { + type: 'text', + initialValue: 'text1', + }, + arrayOf1: { + type: 'arrayOf', + initialValue: [ + { + 'arrayOf1.text1': 'text1', + 'arrayOf1.number1': 10, + }, + ], + fields: { + 'arrayOf1.text1': { + type: 'text', + initialValue: 'default', + }, + 'arrayOf1.number1': { + type: 'number', + initialValue: 0, + }, + }, + }, + }); + expect(result.text1.currentValue).toBe('text1'); + expect(result.text1.initialValue).toBe('text1'); + expect(result.arrayOf1.fields[0]['arrayOf1.text1'].currentValue).toBe( + 'text1', + ); + expect(result.arrayOf1.fields[0]['arrayOf1.text1'].initialValue).toBe( + 'text1', + ); + expect(result.arrayOf1.fields[0]['arrayOf1.number1'].currentValue).toBe(10); + expect(result.arrayOf1.fields[0]['arrayOf1.number1'].initialValue).toBe(10); + }); + it.only('[utils] mapFormFields', () => { + const result = mapFormFields( + { + formDefinition: { + text1: { + type: 'text', + initialValue: 'text1', + }, + arrayOf1: { + type: 'arrayOf', + initialValue: [ + { + 'arrayOf1.text1': 'text1', + 'arrayOf1.number1': 10, + }, + ], + fields: { + 'arrayOf1.text1': { + type: 'text', + initialValue: 'default', + }, + 'arrayOf1.number1': { + type: 'number', + initialValue: 0, + }, + }, + }, + }, + formState: { + text1: { + currentValue: 'changed1', + initialValue: 'text1', + }, + arrayOf1: { + fields: [ + { + 'arrayOf1.text1': { + currentValue: 'arrayOf1.text1.changed1', + initialValue: 'arrayOf1.text1', + }, + 'arrayOf1.number1': { + currentValue: 10, + initialValue: 0, + }, + }, + ], + }, + }, + pathFieldFormDefinition: [], + pathFormState: [], + }, + state => ({ ...state, currentValue: state.initialValue }), + ); + expect(result.text1.currentValue).toBe('text1'); + expect(result.arrayOf1.fields[0]['arrayOf1.text1'].currentValue).toBe( + 'arrayOf1.text1', + ); + expect(result.arrayOf1.fields[0]['arrayOf1.number1'].currentValue).toBe(0); + }); +}); + +describe('[hook] useForm', () => { + it('[hook] enhanceFormFields', () => { + let state; + const setState = updateState => (state = updateState); + const references = { + current: {}, + }; + + const fields = { + text1: { + type: 'text', + initialValue: '', + }, + }; + + const formFields = getFormFields(fields); + const enhancedFields = enhanceFormFields(formFields, { + fields, + references, + setState, + }); + expect(enhancedFields.text1).toBeDefined(); + expect(enhancedFields.text1.type).toBe('text'); + expect(enhancedFields.text1.initialValue).toBe(''); + expect(enhancedFields.text1.value).toBe(''); + expect(enhancedFields.text1.changed).toBeDefined(); + expect(enhancedFields.text1.error).toBeUndefined(); + expect(enhancedFields.text1.setInputRef).toBeDefined(); + expect(enhancedFields.text1.inputRef).toBeUndefined(); + expect(enhancedFields.text1.onChange).toBeDefined(); + }); + + it('[hook] enhanceFormFields', () => { + let state; + const setState = updateState => (state = updateState); + const references = { + current: {}, + }; + + const arrayOfFields = { + 'arrayOf1.text1': { + type: 'text', + initialValue: 'default', + }, + 'arrayOf1.number1': { + type: 'number', + initialValue: 0, + }, + }; + const fields = { + text1: { + type: 'text', + initialValue: '', + }, + arrayOf1: { + type: 'arrayOf', + initialValue: [ + { + 'arrayOf1.text1': 'text1', + 'arrayOf1.number1': 10, + }, + ], + fields: arrayOfFields, + }, + }; + + const formFields = getFormFields(fields); + const enhancedFields = enhanceFormFields(formFields, { + fields, + references, + setState, + }); + expect(enhancedFields.text1).toBeDefined(); + expect(enhancedFields.text1.type).toBe('text'); + expect(enhancedFields.text1.initialValue).toBe(''); + expect(enhancedFields.text1.value).toBe(''); + expect(enhancedFields.text1.changed).toBeDefined(); + expect(enhancedFields.text1.error).toBeUndefined(); + expect(enhancedFields.text1.setInputRef).toBeDefined(); + expect(enhancedFields.text1.inputRef).toBeUndefined(); + expect(enhancedFields.text1.onChange).toBeDefined(); + expect(enhancedFields.arrayOf1).toBeDefined(); + expect(enhancedFields.arrayOf1.fields).toBeDefined(); + expect(enhancedFields.arrayOf1.fields).toHaveLength(1); + expect(enhancedFields.arrayOf1.fields[0]).toBeDefined(); + expect(enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].type).toBe( + 'text', + ); + expect( + enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].initialValue, + ).toBe('text1'); + expect(enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].value).toBe( + 'text1', + ); + expect( + enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].changed, + ).toBeDefined(); + expect( + enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].error, + ).toBeUndefined(); + expect( + enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].setInputRef, + ).toBeDefined(); + expect( + enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].inputRef, + ).toBeUndefined(); + expect( + enhancedFields.arrayOf1.fields[0]['arrayOf1.text1'].onChange, + ).toBeDefined(); + }); +}); + describe('[hook] useForm', () => { it(`[hook] useForm. Verify the initial state`, async () => { const initialFields: FormConfiguration = { @@ -176,6 +424,173 @@ describe('[hook] useForm', () => { expect(result.current.fields.text1.initialValue).toBe(initialFieldValue); }); + it.only(`[hook] useForm. ArrayOf.`, async () => { + const initialFields: FormConfiguration = { + text1: { + type: 'text', + initialValue: '', + }, + arrayOf1: { + type: 'arrayOf', + initialValue: [ + { + 'arrayOf1.text1': 'text1', + 'arrayOf1.number1': 10, + }, + ], + fields: { + 'arrayOf1.text1': { + type: 'text', + initialValue: 'default', + }, + 'arrayOf1.number1': { + type: 'number', + initialValue: 0, + options: { + min: 0, + max: 10, + integer: true, + }, + }, + }, + }, + }; + + const { result } = renderHook(() => useForm(initialFields)); + + // assert initial state + expect(result.current.fields.text1.changed).toBe(false); + expect(result.current.fields.text1.error).toBeUndefined(); + expect(result.current.fields.text1.type).toBe('text'); + expect(result.current.fields.text1.value).toBe(''); + expect(result.current.fields.text1.initialValue).toBe(''); + expect(result.current.fields.text1.onChange).toBeDefined(); + + expect(result.current.fields.arrayOf1).toBeDefined(); + expect(result.current.fields.arrayOf1.fields).toBeDefined(); + expect(result.current.fields.arrayOf1.fields).toHaveLength(1); + expect(result.current.fields.arrayOf1.fields[0]).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].type, + ).toBe('text'); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].initialValue, + ).toBe('text1'); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].value, + ).toBe('text1'); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].changed, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].error, + ).toBeUndefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].setInputRef, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].inputRef, + ).toBeUndefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].onChange, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].type, + ).toBe('number'); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].initialValue, + ).toBe(10); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].value, + ).toBe(10); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].changed, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].error, + ).toBeUndefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].setInputRef, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].inputRef, + ).toBeUndefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].onChange, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].options, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].options.min, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].options.max, + ).toBeDefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.number1'].options + .integer, + ).toBeDefined(); + + // change the input + const changedValue = 'changed_text'; + act(() => { + result.current.fields.text1.onChange({ + target: { + value: changedValue, + }, + }); + }); + + // assert changed state + expect(result.current.fields.text1.changed).toBe(true); + expect(result.current.fields.text1.error).toBeUndefined(); + expect(result.current.fields.text1.value).toBe(changedValue); + expect(result.current.fields.text1.type).toBe('text'); + expect(result.current.fields.text1.initialValue).toBe(''); + + // change arrayOf input + const changedArrayOfValue = 'changed_arrayOf_field'; + act(() => { + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].onChange({ + target: { + value: changedArrayOfValue, + }, + }); + }); + + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].changed, + ).toBe(true); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].error, + ).toBeUndefined(); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].type, + ).toBe('text'); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].value, + ).toBe(changedArrayOfValue); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].initialValue, + ).toBe('text1'); + + // Undo changes + act(() => { + result.current.undoChanges(); + }); + + expect(result.current.fields.text1.value).toBe(''); + expect(result.current.fields.text1.changed).toBe(false); + + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].value, + ).toBe('text1'); + expect( + result.current.fields.arrayOf1.fields[0]['arrayOf1.text1'].changed, + ).toBe(false); + }); + it('[hook] useForm. Verify the hook behavior when receives a custom field type', async () => { const CustomComponent = (props: any) => { const { onChange, field, initialValue } = props; diff --git a/plugins/main/public/components/common/form/hooks.tsx b/plugins/main/public/components/common/form/hooks.tsx index 63ff8fdb72..a4f61cd573 100644 --- a/plugins/main/public/components/common/form/hooks.tsx +++ b/plugins/main/public/components/common/form/hooks.tsx @@ -1,9 +1,8 @@ import { useState, useRef } from 'react'; -import { isEqual } from 'lodash'; +import { isEqual, get, set, cloneDeep } from 'lodash'; import { EpluginSettingType } from '../../../../common/constants'; import { CustomSettingType, - EnhancedFields, FormConfiguration, SettingTypes, UseFormReturn, @@ -36,108 +35,265 @@ const getValueFromEventType: IgetValueFromEventType = { [EpluginSettingType.text]: (event: any) => event.target.value, [EpluginSettingType.textarea]: (event: any) => event.target.value, [EpluginSettingType.number]: (event: any) => event.target.value, + [EpluginSettingType.password]: (event: any) => event.target.value, custom: (event: any) => event.target.value, default: (event: any) => event.target.value, }; -export const useForm = (fields: FormConfiguration): UseFormReturn => { - const [formFields, setFormFields] = useState<{ - [key: string]: { currentValue: any; initialValue: any }; - }>( - Object.entries(fields).reduce( - (accum, [fieldKey, fieldConfiguration]) => ({ +export function getFormFields(fields) { + return Object.entries(fields).reduce( + (accum, [fieldKey, fieldConfiguration]) => { + return { ...accum, [fieldKey]: { - currentValue: fieldConfiguration.initialValue, - initialValue: fieldConfiguration.initialValue, + ...(fieldConfiguration.type === 'arrayOf' + ? { + fields: fieldConfiguration.initialValue.map(item => + getFormFields( + Object.entries(fieldConfiguration.fields).reduce( + (accum, [key]) => ({ + ...accum, + [key]: { + initialValue: item[key], + currentValue: item[key], + defaultValue: fieldConfiguration?.defaultValue, + }, + }), + {}, + ), + ), + ), + } + : { + currentValue: fieldConfiguration.initialValue, + initialValue: fieldConfiguration.initialValue, + defaultValue: fieldConfiguration?.defaultValue, + }), }, - }), - {}, - ), + }; + }, + {}, ); +} - const fieldRefs = useRef<{ [key: string]: any }>({}); +export function enhanceFormFields( + formFields, + { + fields, + references, + setState, + pathFieldParent = [], + pathFormFieldParent = [], + }, +) { + return Object.entries(formFields).reduce( + (accum, [fieldKey, { currentValue: value, ...restFieldState }]) => { + // Define the path to fields object + const pathField = [...pathFieldParent, fieldKey]; + // Define the path to the form fields object + const pathFormField = [...pathFormFieldParent, fieldKey]; + // Get the field form the fields + const field = get(fields, pathField); - const enhanceFields = Object.entries(formFields).reduce( - (accum, [fieldKey, { currentValue: value, ...restFieldState }]) => ({ - ...accum, - [fieldKey]: { - ...fields[fieldKey], - ...restFieldState, - type: fields[fieldKey].type, - value, - changed: !isEqual(restFieldState.initialValue, value), - error: fields[fieldKey]?.validate?.(value), - setInputRef: (reference: any) => { - fieldRefs.current[fieldKey] = reference; - }, - inputRef: fieldRefs.current[fieldKey], - onChange: (event: any) => { - const inputValue = getValueFromEvent(event, fields[fieldKey].type); - const currentValue = - fields[fieldKey]?.transformChangedInputValue?.(inputValue) ?? - inputValue; - setFormFields(state => ({ - ...state, - [fieldKey]: { - ...state[fieldKey], - currentValue, - }, - })); + return { + ...accum, + [fieldKey]: { + ...(field.type === 'arrayOf' + ? { + type: field.type, + fields: (() => { + return restFieldState.fields.map((fieldState, index) => + enhanceFormFields(fieldState, { + fields, + references, + setState, + pathFieldParent: [...pathField, 'fields'], + pathFormFieldParent: [...pathFormField, 'fields', index], + }), + ); + })(), + addNewItem: () => { + setState(state => { + const _state = get(state, [...pathField, 'fields']); + const newstate = set( + state, + [...pathField, 'fields', _state.length], + Object.entries(field.fields).reduce( + (accum, [key, { defaultValue }]) => ({ + ...accum, + [key]: { + currentValue: cloneDeep(defaultValue), + initialValue: cloneDeep(defaultValue), + defaultValue: cloneDeep(defaultValue), + }, + }), + {}, + ), + ); + return cloneDeep(newstate); + }); + }, + } + : { + ...field, + ...restFieldState, + type: field.type, + value, + changed: !isEqual(restFieldState.initialValue, value), + error: field?.validate?.(value), + setInputRef: (reference: any) => { + set(references, pathFormField, reference); + }, + inputRef: get(references, pathFormField), + onChange: (event: any) => { + const inputValue = getValueFromEvent(event, field.type); + const currentValue = + field?.transformChangedInputValue?.(inputValue) ?? + inputValue; + setState(state => { + const newState = set( + cloneDeep(state), + [...pathFormField, 'currentValue'], + currentValue, + ); + return newState; + }); + }, + }), }, - }, - }), + }; + }, {}, ); +} - const changed = Object.fromEntries( - Object.entries(enhanceFields as EnhancedFields) - .filter(([, { changed }]) => changed) - .map(([fieldKey, { value }]) => [ - fieldKey, - fields[fieldKey]?.transformChangedOutputValue?.(value) ?? value, - ]), - ); +export function mapFormFields( + { + formDefinition, + formState, + pathFieldFormDefinition = [], + pathFormState = [], + }, + callbackFormField, +) { + return Object.entries(formState).reduce((accum, [key, value]) => { + const pathField = [...pathFieldFormDefinition, key]; + const fieldDefinition = get(formDefinition, pathField); + return { + ...accum, + [key]: + fieldDefinition.type === 'arrayOf' + ? { + fields: value.fields.map((valueField, index) => + mapFormFields( + { + formDefinition, + formState: valueField, + pathFieldFormDefinition: [...pathField, 'fields'], + pathFormState: [ + ...[...pathFormState, key], + 'fields', + index, + ], + }, + callbackFormField, + ), + ), + } + : callbackFormField?.(value, key, { + formDefinition, + formState, + pathFieldFormDefinition, + pathFormState: [...pathFormState, key], + fieldDefinition, + }), + }; + }, {}); +} - const errors = Object.fromEntries( - Object.entries(enhanceFields as EnhancedFields) - .filter(([, { error }]) => error) - .map(([fieldKey, { error }]) => [fieldKey, error]), - ); +export const useForm = (fields: FormConfiguration): UseFormReturn => { + const [formFields, setFormFields] = useState<{ + [key: string]: { currentValue: any; initialValue: any }; + }>(getFormFields(fields)); + + const fieldRefs = useRef<{ [key: string]: any }>({}); + + const enhanceFields = enhanceFormFields(formFields, { + fields, + references: fieldRefs.current, + setState: setFormFields, + pathFieldParent: [], + pathFormFieldParent: [], + }); + + const { changed, errors } = (() => { + const result = { + changed: {}, + errors: {}, + }; + mapFormFields( + { + formDefinition: fields, + formState: enhanceFields, + pathFieldFormDefinition: [], + pathFormState: [], + }, + ({ changed, error, value }, _, { pathFormState, fieldDefinition }) => { + changed && + (result.changed[pathFormState] = + fieldDefinition?.transformChangedOutputValue?.(value) ?? value); + error && (result.errors[pathFormState] = error); + }, + ); + return result; + })(); function undoChanges() { setFormFields(state => - Object.fromEntries( - Object.entries(state).map(([fieldKey, fieldConfiguration]) => [ - fieldKey, - { - ...fieldConfiguration, - currentValue: fieldConfiguration.initialValue, - }, - ]), + mapFormFields( + { + formDefinition: fields, + formState: state, + pathFieldFormDefinition: [], + pathFormState: [], + }, + state => ({ ...state, currentValue: state.initialValue }), ), ); } function doChanges() { setFormFields(state => - Object.fromEntries( - Object.entries(state).map(([fieldKey, fieldConfiguration]) => [ - fieldKey, - { - ...fieldConfiguration, - initialValue: fieldConfiguration.currentValue, - }, - ]), + mapFormFields( + { + formDefinition: fields, + formState: state, + pathFieldFormDefinition: [], + pathFormState: [], + }, + state => ({ ...state, initialValue: state.currentValue }), ), ); } + function forEach(callback) { + return mapFormFields( + { + formDefinition: fields, + formState: enhanceFields, + pathFieldFormDefinition: [], + pathFormState: [], + }, + callback, + ); + } + return { fields: enhanceFields, changed, errors, undoChanges, doChanges, + forEach, }; }; diff --git a/plugins/main/public/components/common/form/index.tsx b/plugins/main/public/components/common/form/index.tsx index d10797ca0d..08ef95f94d 100644 --- a/plugins/main/public/components/common/form/index.tsx +++ b/plugins/main/public/components/common/form/index.tsx @@ -8,6 +8,7 @@ import { InputFormFilePicker } from './input_filepicker'; import { InputFormTextArea } from './input_text_area'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; import { SettingTypes } from './types'; +import { InputFormPassword } from './input-password'; export interface InputFormProps { type: SettingTypes; @@ -93,5 +94,6 @@ const Input = { select: InputFormSelect, text: InputFormText, textarea: InputFormTextArea, + password: InputFormPassword, custom: ({ component, ...rest }) => component(rest), }; diff --git a/plugins/main/public/components/common/form/input-password.tsx b/plugins/main/public/components/common/form/input-password.tsx new file mode 100644 index 0000000000..eebae6e517 --- /dev/null +++ b/plugins/main/public/components/common/form/input-password.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { EuiFieldPassword } from '@elastic/eui'; +import { IInputFormType } from './types'; + +export const InputFormPassword = ({ + value, + isInvalid, + onChange, + placeholder, + fullWidth, + options, +}: IInputFormType) => { + return ( + + ); +}; diff --git a/plugins/main/public/components/common/form/types.ts b/plugins/main/public/components/common/form/types.ts index 762f73962f..09846abd86 100644 --- a/plugins/main/public/components/common/form/types.ts +++ b/plugins/main/public/components/common/form/types.ts @@ -1,4 +1,4 @@ -import { TPluginSettingWithKey } from '../../../../common/constants'; +import { TPluginSettingWithKey } from '../../../../../wazuh-core/common/constants'; export interface IInputFormType { field: TPluginSettingWithKey; @@ -46,8 +46,18 @@ interface CustomFieldConfiguration extends FieldConfiguration { component: (props: any) => JSX.Element; } +interface ArrayOfFieldConfiguration extends FieldConfiguration { + type: 'arrayOf'; + fields: { + [key: string]: any; // TODO: enhance this type + }; +} + export interface FormConfiguration { - [key: string]: DefaultFieldConfiguration | CustomFieldConfiguration; + [key: string]: + | DefaultFieldConfiguration + | CustomFieldConfiguration + | ArrayOfFieldConfiguration; } interface EnhancedField { @@ -70,7 +80,9 @@ interface EnhancedCustomField extends EnhancedField { component: (props: any) => JSX.Element; } -export type EnhancedFieldConfiguration = EnhancedDefaultField | EnhancedCustomField; +export type EnhancedFieldConfiguration = + | EnhancedDefaultField + | EnhancedCustomField; export interface EnhancedFields { [key: string]: EnhancedFieldConfiguration; } @@ -81,4 +93,15 @@ export interface UseFormReturn { errors: { [key: string]: string }; undoChanges: () => void; doChanges: () => void; + forEach: ( + value: any, + key: string, + form: { + formDefinition: any; + formState: any; + pathFieldFormDefinition: string[]; + pathFormState: string[]; + fieldDefinition: FormConfiguration; + }, + ) => { [key: string]: any }; } diff --git a/plugins/main/public/components/common/hocs/error-boundary/__snapshots__/with-error-boundary.test.tsx.snap b/plugins/main/public/components/common/hocs/error-boundary/__snapshots__/with-error-boundary.test.tsx.snap index 1a84b4ea0f..509fa987ee 100644 --- a/plugins/main/public/components/common/hocs/error-boundary/__snapshots__/with-error-boundary.test.tsx.snap +++ b/plugins/main/public/components/common/hocs/error-boundary/__snapshots__/with-error-boundary.test.tsx.snap @@ -62,7 +62,7 @@ exports[`withErrorBoundary hoc implementation renders correctly to match the sna size="xxl" type="faceSad" > - - + > + + + WrappedComponent => props => { - return condition(props) ? : -} \ No newline at end of file +export const withGuard = + (condition: (props: any) => boolean, ComponentFulfillsCondition) => + WrappedComponent => + props => { + return condition(props) ? ( + + ) : ( + + ); + }; + +export const withGuardAsync = + ( + condition: (props: any) => { ok: boolean; data: any }, + ComponentFulfillsCondition: React.JSX.Element, + ComponentLoadingResolution: null | React.JSX.Element = null, + ) => + WrappedComponent => + props => { + const [loading, setLoading] = useState(true); + const [fulfillsCondition, setFulfillsCondition] = useState({ + ok: false, + data: {}, + }); + + const execCondition = async () => { + try { + setLoading(true); + setFulfillsCondition({ ok: false, data: {} }); + setFulfillsCondition( + await condition({ ...props, check: execCondition }), + ); + } catch (error) { + setFulfillsCondition({ ok: false, data: { error } }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + execCondition(); + }, []); + + if (loading) { + return ComponentLoadingResolution ? ( + + ) : null; + } + + return fulfillsCondition.ok ? ( + + ) : ( + + ); + }; diff --git a/plugins/main/public/components/common/hocs/withUserAuthorization.tsx b/plugins/main/public/components/common/hocs/withUserAuthorization.tsx index fd4ce0bf81..aea01ef14a 100644 --- a/plugins/main/public/components/common/hocs/withUserAuthorization.tsx +++ b/plugins/main/public/components/common/hocs/withUserAuthorization.tsx @@ -10,21 +10,43 @@ * Find more information about this on the LICENSE file. */ -import React from "react"; -import { useUserPermissions, useUserPermissionsRequirements, useUserPermissionsPrivate } from '../hooks/useUserPermissions'; -import { useUserRoles, useUserRolesRequirements, useUserRolesPrivate } from '../hooks/useUserRoles'; +import React from 'react'; +import { useUserPermissionsRequirements } from '../hooks/useUserPermissions'; +import { useUserPermissionsIsAdminRequirements } from '../hooks/use-user-is-admin'; import { WzEmptyPromptNoPermissions } from '../permissions/prompt'; import { compose } from 'redux'; -import { withUserLogged } from './withUserLogged' - // - const withUserAuthorizationPromptChanged = (permissions = null, roles = null) => WrappedComponent => props => { - const [userPermissionRequirements, userPermissions] = useUserPermissionsRequirements(typeof permissions === 'function' ? permissions(props) : permissions); - const [userRolesRequirements, userRoles] = useUserRolesRequirements(typeof roles === 'function' ? roles(props) : roles); +import { withUserLogged } from './withUserLogged'; +// +const withUserAuthorizationPromptChanged = + (permissions = null, othersPermissions = { isAdmininistrator: null }) => + WrappedComponent => + props => { + const [userPermissionRequirements, userPermissions] = + useUserPermissionsRequirements( + typeof permissions === 'function' ? permissions(props) : permissions, + ); + const [_userPermissionIsAdminRequirements] = + useUserPermissionsIsAdminRequirements(); - return (userPermissionRequirements || userRolesRequirements) ? : ; -} + const userPermissionIsAdminRequirements = + othersPermissions?.isAdmininistrator + ? _userPermissionIsAdminRequirements + : null; -export const withUserAuthorizationPrompt = (permissions = null, roles = null) => WrappedComponent => compose( - withUserLogged, - withUserAuthorizationPromptChanged(permissions,roles) - )(WrappedComponent) + return userPermissionRequirements || userPermissionIsAdminRequirements ? ( + + ) : ( + + ); + }; + +export const withUserAuthorizationPrompt = + (permissions = null, othersPermissions = { isAdmininistrator: null }) => + WrappedComponent => + compose( + withUserLogged, + withUserAuthorizationPromptChanged(permissions, othersPermissions), + )(WrappedComponent); diff --git a/plugins/main/public/components/common/hocs/withUserRoles.tsx b/plugins/main/public/components/common/hocs/withUserRoles.tsx deleted file mode 100644 index d6823980d9..0000000000 --- a/plugins/main/public/components/common/hocs/withUserRoles.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Wazuh app - React HOCs to manage user role requirements - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -import React from "react"; -import { useUserRoles, useUserRolesRequirements, useUserRolesPrivate } from '../hooks/useUserRoles'; - -// This HOC passes rolesValidation to wrapped component -export const withUserRoles = WrappedComponent => props => { - const userRoles = useUserRoles(); - return ; -} - -// This HOC hides the wrapped component if user has not permissions -export const withUserRolesRequirements = requiredUserRoles => WrappedComponent => props => { - const [userRolesRequirements, userRoles] = useUserRolesRequirements(typeof requiredUserRoles === 'function' ? requiredUserRoles(props) : requiredUserRoles); - return ; -} - -// This HOC redirects to redirectURL if user has not permissions -export const withUserRolesPrivate = (requiredUserRoles, redirectURL) => WrappedComponent => props => { - const [userRolesRequirements, userRoles] = useUserRolesPrivate(requiredUserRoles, redirectURL); - return userRolesRequirements ? : null; -} - diff --git a/plugins/main/public/components/common/hooks/index.ts b/plugins/main/public/components/common/hooks/index.ts index e3ce7584c7..f8e404da53 100644 --- a/plugins/main/public/components/common/hooks/index.ts +++ b/plugins/main/public/components/common/hooks/index.ts @@ -17,7 +17,7 @@ export * from './use-query'; export * from './use-time-filter'; export * from './useWindowSize'; export * from './useUserPermissions'; -export * from './useUserRoles'; +export * from './use-user-is-admin'; export * from './useResfreshAngularDiscover'; export * from './useAllowedAgents'; export * from './useApiRequest'; diff --git a/plugins/main/public/components/common/hooks/use-service.test.tsx b/plugins/main/public/components/common/hooks/use-service.test.tsx new file mode 100644 index 0000000000..fbfe48570c --- /dev/null +++ b/plugins/main/public/components/common/hooks/use-service.test.tsx @@ -0,0 +1,68 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useService } from './use-service'; + +const successfulService = async (_params?: any) => { + return Promise.resolve('test data'); +}; +const failingService = async (_params?: any) => { + return Promise.reject('Error occurred'); +}; +const successfulRefreshService = async (_params?: any) => { + return Promise.resolve(Date.now()); +}; + +const successfulServiceWithParams = jest.fn().mockResolvedValue('test data'); + +describe('useService hook', () => { + it('should return data and success state when service call is successful', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useService(successfulService), + ); + + await waitForNextUpdate(); + + expect(result.current.data).toEqual('test data'); + expect(result.current.isLoading).toEqual(false); + expect(result.current.isSuccess).toEqual(true); + expect(result.current.isError).toEqual(false); + expect(result.current.error).toBeUndefined(); + }); + + it('should return error state when service call fails', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useService(failingService), + ); + + await waitForNextUpdate(); + + expect(result.current.data).toBeUndefined(); + expect(result.current.isLoading).toEqual(false); + expect(result.current.isSuccess).toEqual(false); + expect(result.current.isError).toEqual(true); + expect(result.current.error).toEqual('Error occurred'); + }); + + it('should fetch data again with new data when refresh value changes', async () => { + const { result, rerender, waitForNextUpdate } = renderHook( + ({ refresh }) => useService(successfulRefreshService, undefined, refresh), + { initialProps: { refresh: 0 } }, + ); + + await waitForNextUpdate(); + + const data = result.current.data; + + rerender({ refresh: 1 }); + await waitForNextUpdate(); + + expect(result.current.data).not.toEqual(data); + }); + + it('should call the service with provided parameters', async () => { + const params = { id: 123456 }; + + renderHook(() => useService(successfulServiceWithParams, params)); + + expect(successfulServiceWithParams).toHaveBeenCalledWith(params); + }); +}); diff --git a/plugins/main/public/components/common/hooks/use-service.ts b/plugins/main/public/components/common/hooks/use-service.ts new file mode 100644 index 0000000000..979b787213 --- /dev/null +++ b/plugins/main/public/components/common/hooks/use-service.ts @@ -0,0 +1,40 @@ +import { useState, useEffect } from 'react'; + +interface useServiceResponse { + data: T | undefined; + isLoading: boolean; + isSuccess: boolean; + isError: boolean; + error: any; +} + +export function useService( + service: (params?: any | undefined) => Promise, + params?: any | undefined, + refresh?: number | undefined, +): useServiceResponse { + const [data, setData] = useState(undefined); + const [isLoading, setIsLoading] = useState(true); + const [isSuccess, setIsSuccess] = useState(false); + const [isError, setIsError] = useState(false); + const [error, setError] = useState(undefined); + useEffect(() => { + const handleService = async () => { + setIsLoading(true); + try { + const response = await service(params); + setData(response); + setIsSuccess(true); + } catch (error) { + setIsError(true); + setError(error); + } finally { + setIsLoading(false); + } + }; + + handleService(); + }, [refresh]); + + return { data, isLoading, isSuccess, isError, error }; +} diff --git a/plugins/main/public/components/common/hooks/use-user-is-admin.ts b/plugins/main/public/components/common/hooks/use-user-is-admin.ts new file mode 100644 index 0000000000..65c8e98102 --- /dev/null +++ b/plugins/main/public/components/common/hooks/use-user-is-admin.ts @@ -0,0 +1,7 @@ +import { useSelector } from 'react-redux'; + +// It retuns user requirements if is is not admin +export const useUserPermissionsIsAdminRequirements = () => { + const account = useSelector(state => state.appStateReducers.userAccount); + return [account.administrator_error_message, account]; +}; diff --git a/plugins/main/public/components/common/hooks/useUserRoles.ts b/plugins/main/public/components/common/hooks/useUserRoles.ts deleted file mode 100644 index f986eee57f..0000000000 --- a/plugins/main/public/components/common/hooks/useUserRoles.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Wazuh app - React hooks to manage user role requirements - * 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 { useSelector } from 'react-redux'; -import { WzUserPermissions } from '../../../react-services/wz-user-permissions'; - -// It retuns user Roles -export const useUserRoles = () => { - const userRoles = useSelector(state => state.appStateReducers.userRoles); - return userRoles; -} - -// It returns user roles validation and user roles -export const useUserRolesRequirements = (requiredRoles) => { - const userRoles = useUserRoles(); - if(requiredRoles === null){ - return [false, userRoles] - } - const requiredRolesArray = typeof requiredRoles === 'function' ? requiredRoles() : requiredRoles; - return [WzUserPermissions.checkMissingUserRoles(requiredRolesArray, userRoles), userRoles]; -} - -// It redirects to other URL if user roles are not valid -export const useUserRolesPrivate = (requiredRoles, redirectURL) => { - const [userRolesValidation, userRoles] = useUserRolesRequirements(requiredRoles); - if(userRolesValidation){ - window.location.href = redirectURL; - } - return [userRolesValidation, userRoles]; -} \ No newline at end of file diff --git a/plugins/main/public/components/common/loading/loading-spinner-data-source.tsx b/plugins/main/public/components/common/loading/loading-spinner-data-source.tsx new file mode 100644 index 0000000000..36c4b49930 --- /dev/null +++ b/plugins/main/public/components/common/loading/loading-spinner-data-source.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + EuiTitle, + EuiPanel, + EuiEmptyPrompt, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { FormattedMessage } from '@osd/i18n/react'; +import { IntlProvider } from 'react-intl'; + +export function LoadingSpinnerDataSource() { + return ( + + + } + title={ + +

+ +

+
+ } + /> +
+
+ ); +} diff --git a/plugins/main/public/components/common/modules/events-enhance-discover-fields.ts b/plugins/main/public/components/common/modules/events-enhance-discover-fields.ts deleted file mode 100644 index e5e5f5fc97..0000000000 --- a/plugins/main/public/components/common/modules/events-enhance-discover-fields.ts +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Wazuh app - Integrity monitoring components - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -import { FlyoutDetail } from '../../../components/agents/fim/inventory/flyout'; -import { FlyoutTechnique } from '../../overview/mitre/components/techniques/components/flyout-technique'; -import { AppNavigate } from '../../../react-services/app-navigate'; -import { getCore } from '../../../kibana-services'; - -// Field to add to elements enchanced -const CUSTOM_ATTRIBUTE_ENHANCED_DISCOVER_FIELD = - 'data-wazuh-discover-field-enhanced'; - -type TGetFlyoutProps = (content: string, rowData, options) => any; - -// Set attributes (as object) in a HTML element -const addAttributesToElement = (element, attributes: any, ...options) => { - Object.keys(attributes).forEach(attribute => { - const attributeValue: - | ((...options) => string | undefined) - | string - | undefined = attributes[attribute]; - const attributeValueResult = - typeof attributeValue === 'function' - ? attributeValue(...options) - : attributeValue; - if (attributeValueResult) { - element.setAttribute(attribute, attributeValueResult); - } - }); -}; - -// Enhance field: create an anchor HTML element that redirect to some URL -const createElementFieldURLRedirection = - (attributes?: any) => (content: string, rowData, element, options) => { - const container = document.createElement('a'); - addAttributesToElement(container, attributes, content, rowData, options); - container.textContent = content; - return container.getAttribute('href') ? container : undefined; - }; - -// Enhance field: create an anchor HTML element that open a flyout React component -const createElementFieldOpenFlyout = - (FlyoutComponent, getFlyoutProps: TGetFlyoutProps) => - (content: string, rowData, element, options) => { - const container = document.createElement('a'); - container.onclick = () => { - options.setFlyout({ - component: FlyoutComponent, - props: getFlyoutProps(content, rowData, options), - }); - }; - container.textContent = content; - return container; - }; - -// Enhance field: create a div HTML element with anchor HTML elements in the same cell to open a flyout React component with different data -const createElementFieldOpenFlyoutMultiple = - (FlyoutComponent, getFlyoutProps: TGetFlyoutProps, containerOptions) => - (content: string, rowData, element, options) => { - const container = document.createElement(containerOptions.element || 'div'); - const formattedContent = content.match(containerOptions.contentRegex); - if (!formattedContent) { - return; - } - const createElementFieldOpenFlyoutCreator = createElementFieldOpenFlyout( - FlyoutComponent, - getFlyoutProps, - ); - formattedContent.forEach((item, itemIndex) => { - const itemElement = createElementFieldOpenFlyoutCreator( - item, - rowData, - element, - options, - ); - container.appendChild(itemElement); - if (itemIndex < formattedContent.length - 1) { - const separatorElement = document.createElement( - containerOptions.separatorElement || 'span', - ); - separatorElement.textContent = containerOptions.separator || ', '; - container.appendChild(separatorElement); - } - }); - return container; - }; - -// Returns the style attribute value as string -const styleObjectToString = (obj: any) => - Object.keys(obj) - .map(key => `${key}: ${obj[key]}`) - .join('; '); - -// Styles for external links (as object) -const attributesExternalLink = { - target: '__blank', - rel: 'noreferrer', -}; - -// Define button styles -const buttonStyles = attributesExternalLink; - -export const EventsEnhanceDiscoverCell = { - 'rule.id': createElementFieldURLRedirection({ - ...buttonStyles, - href: content => - getCore().application.getUrlForApp('rules', { - path: `#/manager/rules?tab=rules&redirectRule=${content}`, - }), - }), - 'agent.id': createElementFieldURLRedirection({ - ...buttonStyles, - href: content => - content !== '000' - ? getCore().application.getUrlForApp('endpoints-summary', { - path: `#/agents?tab=welcome&agent=${content}`, - }) - : undefined, - }), - 'agent.name': createElementFieldURLRedirection({ - ...buttonStyles, - href: (content, rowData) => { - const agentId = (((rowData || {})._source || {}).agent || {}).id; - return agentId !== '000' - ? getCore().application.getUrlForApp('endpoints-summary', { - path: `#/agents?tab=welcome&agent=${agentId}`, - }) - : undefined; - }, - }), - 'syscheck.path': createElementFieldOpenFlyout( - FlyoutDetail, - (content, rowData, options) => ({ - fileName: content, - agentId: (((rowData || {})._source || {}).agent || {}).id, - type: 'file', - view: 'events', - closeFlyout: options.closeFlyout, - }), - ), - 'rule.mitre.id': createElementFieldOpenFlyoutMultiple( - FlyoutTechnique, - (content, rowData, options) => ({ - openDiscover(e, techniqueID) { - AppNavigate.navigateToModule(e, 'overview', { - tab: 'mitre', - tabView: 'discover', - filters: { 'rule.mitre.id': techniqueID }, - }); - }, - openDashboard(e, techniqueID) { - AppNavigate.navigateToModule(e, 'overview', { - tab: 'mitre', - tabView: 'dashboard', - filters: { 'rule.mitre.id': techniqueID }, - }); - }, - onChangeFlyout(isFlyoutVisible) { - isFlyoutVisible ? null : options.closeFlyout(); - }, - currentTechnique: content, - }), - { - contentRegex: /(T\d+\.?(\d+)?)/g, - element: 'span', - }, - ), - 'syscheck.value_name': (content, rowData, element, options) => { - if (content) return; - const container = document.createElement('span'); - container.insertAdjacentHTML( - 'beforeend', - '', - ); - container.insertAdjacentHTML( - 'beforeend', - 'Empty field', - ); - return container; - }, -}; - -// Method to enhance a cell of discover table -export const enhanceDiscoverEventsCell = ( - field, - content, - rowData, - element, - options, -) => { - if ( - !EventsEnhanceDiscoverCell[field] || - !element || - element.attributes[CUSTOM_ATTRIBUTE_ENHANCED_DISCOVER_FIELD] - ) { - return; - } - const elementCellEnhanced = EventsEnhanceDiscoverCell[field]( - content, - rowData, - element, - options, - ); - if (elementCellEnhanced) { - elementCellEnhanced.setAttribute( - CUSTOM_ATTRIBUTE_ENHANCED_DISCOVER_FIELD, - '', - ); // Set a custom attribute to indentify that element was enhanced - element.replaceWith(elementCellEnhanced); - } -}; diff --git a/plugins/main/public/components/common/modules/events-selected-fields.js b/plugins/main/public/components/common/modules/events-selected-fields.js deleted file mode 100644 index ee685c42e7..0000000000 --- a/plugins/main/public/components/common/modules/events-selected-fields.js +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Wazuh app - Simple description for each App tabs - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ -export const EventsSelectedFiles = { - fim: [ - 'agent.name', - 'syscheck.path', - 'syscheck.event', - 'rule.description', - 'rule.level', - 'rule.id' - ], - mitre: [ - 'agent.name', - 'rule.mitre.id', - 'rule.mitre.tactic', - 'rule.description', - 'rule.level', - 'rule.id' - ], - sca: [ - 'data.sca.check.title', - 'data.sca.check.file', - 'data.sca.check.result', - 'data.sca.policy', - ], - general: [ - 'agent.name', - 'rule.description', - 'rule.level', - 'rule.id', - ], - aws: [ - 'data.aws.source', - 'rule.description', - 'rule.level', - 'rule.id', - ], - gcp: [ - 'agent.name', - 'data.gcp.jsonPayload.vmInstanceName', - 'data.gcp.resource.labels.location', - 'data.gcp.resource.labels.project_id', - 'data.gcp.resource.type', - 'data.gcp.severity', - ], - pm: [ - 'agent.name', - 'data.title', - 'rule.description', - 'rule.level', - 'rule.id', - ], - audit: [ - 'agent.name', - 'data.audit.command', - 'data.audit.pid', - 'rule.description', - 'rule.level', - 'rule.id', - ], - oscap: [ - 'agent.name', - 'data.oscap.check.title', - 'data.oscap.check.description', - 'data.oscap.check.result', - 'data.oscap.check.severity', - ], - ciscat: [ - 'agent.name', - 'data.cis.benchmark', - 'data.cis.group', - 'data.cis.pass', - 'data.cis.fail', - 'data.cis.unknown', - 'data.cis.result', - ], - vuls: [ - 'agent.name', - 'data.vulnerability.package.name', - 'data.vulnerability.cve', - 'data.vulnerability.severity', - 'data.vulnerability.status' - ], - virustotal: [ - 'agent.name', - 'data.virustotal.source.file', - 'data.virustotal.permalink', - 'data.virustotal.malicious', - 'data.virustotal.positives', - 'data.virustotal.total', - ], - osquery: [ - 'agent.name', - 'data.osquery.name', - 'data.osquery.pack', - 'data.osquery.action', - 'data.osquery.subquery', - ], - docker: [ - 'agent.name', - 'data.docker.from', - 'data.docker.Type', - 'data.docker.Action', - 'rule.description', - 'rule.level', - ], - pci: [ - 'agent.name', - 'rule.pci_dss', - 'rule.description', - 'rule.level', - 'rule.id' - ], - gdpr: [ - 'agent.name', - 'rule.gdpr', - 'rule.description', - 'rule.level', - 'rule.id' - ], - hipaa: [ - 'agent.name', - 'rule.hipaa', - 'rule.description', - 'rule.level', - 'rule.id' - ], - nist: [ - 'agent.name', - 'rule.nist_800_53', - 'rule.description', - 'rule.level', - 'rule.id' - ], - tsc: [ - 'agent.name', - 'rule.tsc', - 'rule.description', - 'rule.level', - 'rule.id' - ], - office: [ - 'data.office365.Subscription', - 'data.office365.Operation', - 'data.office365.UserId', - 'data.office365.ClientIP', - 'rule.level', - 'rule.id' - ], - github: [ - 'agent.id', - 'data.github.repo', - 'data.github.actor', - 'data.github.org', - 'rule.description', - 'rule.level', - 'rule.id' - ], - -}; diff --git a/plugins/main/public/components/common/modules/events.tsx b/plugins/main/public/components/common/modules/events.tsx deleted file mode 100644 index 0005eeb9d6..0000000000 --- a/plugins/main/public/components/common/modules/events.tsx +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Wazuh app - Integrity monitoring components - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -import React, { Component, Fragment } from 'react'; -import { getAngularModule, getToasts } from '../../../kibana-services'; -import { EventsSelectedFiles } from './events-selected-fields'; -import { ModulesHelper } from './modules-helper'; -import store from '../../../redux/store'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiOverlayMask, - EuiOutsideClickDetector, -} from '@elastic/eui'; -import { PatternHandler } from '../../../react-services/pattern-handler'; -import { enhanceDiscoverEventsCell } from './events-enhance-discover-fields'; -import { toMountPoint } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; -import { withAgentSupportModule, withModuleTabLoader } from '../hocs'; -import { compose } from 'redux'; -import { UI_LOGGER_LEVELS } from '../../../../common/constants'; -import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; -import { getErrorOrchestrator } from '../../../react-services/common-services'; - -export const Events = compose( - withAgentSupportModule, - withModuleTabLoader -)( - class Events extends Component { - intervalCheckExistsDiscoverTableTime: number = 200; - isMount: boolean; - hasRefreshedKnownFields: boolean; - isRefreshing: boolean; - state: { - flyout: false | { component: any; props: any }; - discoverRowsData: any[]; - }; - constructor(props) { - super(props); - this.isMount = true; - this.hasRefreshedKnownFields = false; - this.isRefreshing = false; - this.state = { - flyout: false, - discoverRowsData: [], - }; - } - - async componentDidMount() { - document.body.scrollTop = 0; // For Safari - document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera - const app = getAngularModule(); - this.$rootScope = app.$injector.get('$rootScope'); - this.$rootScope.showModuleEvents = this.props.section; - const scope = await ModulesHelper.getDiscoverScope(); - if (this.isMount) { - this.$rootScope.moduleDiscoverReady = true; - this.$rootScope.$applyAsync(); - const fields = [...EventsSelectedFiles[this.props.section]]; - const index = fields.indexOf('agent.name'); - if (index > -1 && store.getState().appStateReducers.currentAgentData.id) { - //if an agent is pinned we don't show the agent.name column - fields.splice(index, 1); - } - if (fields) { - scope.state.columns = fields; - scope.addColumn(false); - scope.removeColumn(false); - } - this.fetchWatch = scope.$watchCollection('fetchStatus', (fetchStatus) => { - if (scope.fetchStatus === 'complete') { - setTimeout(() => { - ModulesHelper.cleanAvailableFields(); - }, 1000); - // Check the discover table is in the DOM and enhance the initial table cells - this.intervalCheckExistsDiscoverTable = setInterval(() => { - const discoverTableTBody = document.querySelector('.kbn-table tbody'); - if (discoverTableTBody) { - const options = { setFlyout: this.setFlyout, closeFlyout: this.closeFlyout }; - this.enhanceDiscoverTableCurrentRows(this.state.discoverRowsData, options, true); - this.enhanceDiscoverTableAddObservers(options); - clearInterval(this.intervalCheckExistsDiscoverTable); - } - }, this.intervalCheckExistsDiscoverTableTime); - } - this.setState({ discoverRowsData: scope.rows }); - }); - } - } - - componentWillUnmount() { - this.isMount = false; - if (this.fetchWatch) this.fetchWatch(); - this.$rootScope.showModuleEvents = false; - this.$rootScope.moduleDiscoverReady = false; - this.$rootScope.$applyAsync(); - this.discoverTableRowsObserver && this.discoverTableRowsObserver.disconnect(); - this.discoverTableColumnsObserver && this.discoverTableColumnsObserver.disconnect(); - this.intervalCheckExistsDiscoverTableTime && - clearInterval(this.intervalCheckExistsDiscoverTable); - } - - enhanceDiscoverTableAddObservers = (options) => { - // Scrolling table observer, when load more events - this.discoverTableRowsObserver = new MutationObserver((mutationsList) => { - mutationsList.forEach((mutation) => { - if (mutation.type === 'childList' && mutation.addedNodes && mutation.addedNodes[0]) { - this.enhanceDiscoverTableScrolling( - mutation.addedNodes[0], - this.state.discoverRowsData, - options - ); - } - }); - }); - const discoverTableTBody = document.querySelector('.kbn-table tbody'); - this.discoverTableRowsObserver.observe(discoverTableTBody, { childList: true }); - - // Add observer when add or remove table header column - this.discoverTableColumnsObserver = new MutationObserver((mutationsList) => { - mutationsList.forEach((mutation) => { - if (mutation.type === 'childList') { - this.enhanceDiscoverTableCurrentRows(this.state.discoverRowsData, options); - } - }); - }); - const discoverTableElement = document.querySelector('.kbn-table').parentElement.parentElement - .parentElement; - this.discoverTableColumnsObserver.observe(discoverTableElement, { childList: true }); - }; - - enhanceDiscoverTableCurrentRows = (discoverRowsData, options, addObserverDetails = false) => { - // Get table headers - const discoverTableHeaders = document.querySelectorAll(`.kbn-table thead th`); - // Get table rows - const discoverTableRows = document.querySelectorAll(`.kbn-table tbody tr.kbnDocTable__row`); - - discoverTableRows.forEach((row, rowIndex) => { - // Enhance each cell of table rows - discoverTableHeaders.forEach((header, headerIndex) => { - const cell = row.querySelector(`td:nth-child(${headerIndex + 1}) div`); - if (cell) { - enhanceDiscoverEventsCell( - header.textContent, - cell.textContent, - discoverRowsData[rowIndex], - cell, - options - ); - } - }); - // Add observer to row details - if (addObserverDetails) { - const rowDetails = row.nextElementSibling; - this.enhanceDiscoverTableRowDetailsAddObserver(rowDetails, discoverRowsData, options); - } - }); - }; - - checkDiscoverTableDetailsMutation(element, mutation, discoverRowsData, options) { - const rowTable = element.previousElementSibling; - const discoverTableRows = document.querySelectorAll(`.kbn-table tbody tr.kbnDocTable__row`); - const rowIndex = Array.from(discoverTableRows).indexOf(rowTable); - const rowDetailsFields = mutation.addedNodes[0].querySelectorAll('.kbnDocViewer__field'); - let hasUnknownFields = false; - if (rowDetailsFields) { - rowDetailsFields.forEach(async (rowDetailField, i) => { - //check for unknown fields until 1 unknown field is found - if (!hasUnknownFields && this.checkUnknownFields(rowDetailField)) - hasUnknownFields = true; - const fieldName = rowDetailField.childNodes[0].childNodes[1].textContent || ''; - const fieldCell = - rowDetailField.parentNode.childNodes && - rowDetailField.parentNode.childNodes[2].childNodes[0]; - if (!fieldCell) { - return; - } - enhanceDiscoverEventsCell( - fieldName, - (fieldCell || {}).textContent || '', - discoverRowsData[rowIndex], - fieldCell, - options - ); - }); - if (hasUnknownFields) { - this.refreshKnownFields(); - } - } - } - - checkUnknownFields(rowDetailField) { - const fieldCell = - rowDetailField.parentNode.childNodes && rowDetailField.parentNode.childNodes[2]; - return (fieldCell.querySelector('svg[data-test-subj="noMappingWarning"]')) - } - - refreshKnownFields = async () => { - if (!this.hasRefreshedKnownFields) { - try { - this.hasRefreshedKnownFields = true; - this.isRefreshing = true; - await PatternHandler.refreshIndexPattern(); - this.isRefreshing = false; - this.reloadToast(); - } catch (error) { - this.isRefreshing = false; - throw error; - } - } else if (this.isRefreshing) { - await new Promise((r) => setTimeout(r, 150)); - await this.refreshKnownFields(); - } - }; - - enhanceDiscoverTableRowDetailsAddObserver(element, discoverRowsData, options) { - // Open for first time the row details - const observer = new MutationObserver((mutationsList) => { - mutationsList.forEach((mutation) => { - if (mutation.type === 'childList' && mutation.addedNodes && mutation.addedNodes[0]) { - this.checkDiscoverTableDetailsMutation(element, mutation, discoverRowsData, options); - // Add observer when switch between the tabs of Table and JSON - new MutationObserver((mutationList) => { - if ( - mutation.addedNodes[0] - .querySelector('div[role=tabpanel]') - .getAttribute('aria-labelledby') === 'kbn_doc_viewer_tab_0' - ) { - this.checkDiscoverTableDetailsMutation( - element, - mutation, - discoverRowsData, - options - ); - } - }).observe(mutation.addedNodes[0].querySelector('div[role=tabpanel]'), { - attributes: true, - }); - } - }); - }); - observer.observe(element, { childList: true }); - } - - enhanceDiscoverTableScrolling = (mutationElement, discoverRowsData, options) => { - // Get table headers - const discoverTableHeaders = document.querySelectorAll(`.kbn-table thead th`); - // Get table rows - const discoverTableRows = document.querySelectorAll(`.kbn-table tbody tr.kbnDocTable__row`); - try { - const rowIndex = Array.from(discoverTableRows).indexOf(mutationElement); - if (rowIndex !== -1) { - // It is a discover table row - discoverTableHeaders.forEach((header, headerIndex) => { - const cell = mutationElement.querySelector(`td:nth-child(${headerIndex + 1}) div`); - if (!cell) { - return; - } - enhanceDiscoverEventsCell( - header.textContent, - cell.textContent, - discoverRowsData[rowIndex], - cell, - options - ); - }); - } else { - // It is a details table row - this.enhanceDiscoverTableRowDetailsAddObserver( - mutationElement, - discoverRowsData, - options - ); - } - } catch (error) { - const options = { - context: `${Events.name}.hideCreateCustomLabel`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - getErrorOrchestrator().handleError(options); - } - }; - - setFlyout = (flyout) => { - this.setState({ flyout }); - }; - - closeFlyout = () => { - this.setState({ flyout: false }); - }; - - reloadToast = () => { - const toastLifeTimeMs = 300000; - getToasts().add({ - color: 'success', - title: 'The index pattern was refreshed successfully.', - text: toMountPoint( - - - There were some unknown fields for the current index pattern. You need to refresh - the page to apply the changes. - - - window.location.reload()} size="s"> - Reload page - - - - ), - toastLifeTimeMs, - }); - }; - - errorToast = (error) => { - getToasts().add({ - color: 'danger', - title: 'The index pattern could not be refreshed.', - text: - 'There are some unknown fields for the current index pattern. The index pattern fields need to be refreshed.', - }); - }; - - render() { - const { flyout } = this.state; - const FlyoutComponent = (flyout || {}).component; - return ( - - {flyout && ( - - )} - - ); - } - } -); diff --git a/plugins/main/public/components/common/modules/index.ts b/plugins/main/public/components/common/modules/index.ts index 1c8866e5ba..4809b5207c 100644 --- a/plugins/main/public/components/common/modules/index.ts +++ b/plugins/main/public/components/common/modules/index.ts @@ -11,5 +11,4 @@ */ export * from './dashboard'; -export * from './events'; export * from './modules-helper.js'; diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index b365d7fe74..80de04594c 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.tsx +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -10,7 +10,6 @@ * Find more information about this on the LICENSE file. */ import { Dashboard } from './dashboard'; -import { Events } from './events'; import { MainSca } from '../../agents/sca'; import { MainMitre } from './main-mitre'; import { ModuleMitreAttackIntelligence } from '../../overview/mitre_attack_intelligence'; @@ -39,6 +38,14 @@ import { nistColumns } from '../../overview/nist/events/nist-columns'; import { gdprColumns } from '../../overview/gdpr/events/gdpr-columns'; import { tscColumns } from '../../overview/tsc/events/tsc-columns'; import { githubColumns } from '../../overview/github-panel/events/github-columns'; +import { mitreAttackColumns } from '../../overview/mitre/events/mitre-attack-columns'; +import { virustotalColumns } from '../../overview/virustotal/events/virustotal-columns'; +import { malwareDetectionColumns } from '../../overview/malware-detection/events/malware-detection-columns'; +import { WAZUH_VULNERABILITIES_PATTERN } from '../../../../common/constants'; +import { withVulnerabilitiesStateDataSource } from '../../overview/vulnerabilities/common/hocs/validate-vulnerabilities-states-index-pattern'; + +const ALERTS_INDEX_PATTERN = 'wazuh-alerts-*'; +const DEFAULT_INDEX_PATTERN = ALERTS_INDEX_PATTERN; const DashboardTab = { id: 'dashboard', @@ -58,13 +65,6 @@ const renderDiscoverTab = (columns, indexPattern) => { }; }; -const EventsTab = { - id: 'events', - name: 'Events', - buttons: [ButtonModuleExploreAgent], - component: Events, -}; - const RegulatoryComplianceTabs = columns => [ DashboardTab, { @@ -120,14 +120,13 @@ export const ModulesDefaults = { ], availableFor: ['manager', 'agent'], }, + // This module is Malware Detection. Ref: https://github.com/wazuh/wazuh-dashboard-plugins/issues/5893 pm: { init: 'dashboard', - tabs: [DashboardTab, EventsTab], - availableFor: ['manager', 'agent'], - }, - audit: { - init: 'dashboard', - tabs: [DashboardTab, EventsTab], + tabs: [ + DashboardTab, + renderDiscoverTab(DEFAULT_INDEX_PATTERN, malwareDetectionColumns), + ], availableFor: ['manager', 'agent'], }, sca: { @@ -190,11 +189,6 @@ export const ModulesDefaults = { ], availableFor: ['manager', 'agent'], }, - ciscat: { - init: 'dashboard', - tabs: [DashboardTab, EventsTab], - availableFor: ['manager', 'agent'], - }, vuls: { init: 'dashboard', tabs: [ @@ -202,17 +196,33 @@ export const ModulesDefaults = { id: 'dashboard', name: 'Dashboard', component: DashboardVuls, - buttons: [ButtonModuleExploreAgent], + /* For ButtonModuleExploreAgent to insert correctly according to the module's index pattern, the moduleIndexPatternTitle parameter is added. By default it applies the index patternt wazuh-alerts-* */ + buttons: [ + ({ ...props }) => ( + + ), + ], }, { id: 'inventory', name: 'Inventory', component: InventoryVuls, - buttons: [ButtonModuleExploreAgent], + /* For ButtonModuleExploreAgent to insert correctly according to the module's index pattern, the moduleIndexPatternTitle parameter is added. By default it applies the index patternt wazuh-alerts-* */ + buttons: [ + ({ ...props }) => ( + + ), + ], }, { ...renderDiscoverTab(vulnerabilitiesColumns), - component: withModuleNotForAgent(() => ( + component: withVulnerabilitiesStateDataSource(() => ( @@ -237,13 +247,16 @@ export const ModulesDefaults = { buttons: [ButtonModuleExploreAgent], component: MainMitre, }, - EventsTab, + renderDiscoverTab(DEFAULT_INDEX_PATTERN, mitreAttackColumns), ], availableFor: ['manager', 'agent'], }, virustotal: { init: 'dashboard', - tabs: [DashboardTab, EventsTab], + tabs: [ + DashboardTab, + renderDiscoverTab(DEFAULT_INDEX_PATTERN, virustotalColumns), + ], availableFor: ['manager', 'agent'], }, docker: { @@ -254,17 +267,6 @@ export const ModulesDefaults = { ], availableFor: ['manager', 'agent'], }, - - osquery: { - init: 'dashboard', - tabs: [DashboardTab, EventsTab], - availableFor: ['manager', 'agent'], - }, - oscap: { - init: 'dashboard', - tabs: [DashboardTab, EventsTab], - availableFor: ['manager', 'agent'], - }, pci: { init: 'dashboard', tabs: RegulatoryComplianceTabs(pciColumns), diff --git a/plugins/main/public/components/common/modules/modules-helper.js b/plugins/main/public/components/common/modules/modules-helper.js index 91ebc10f87..0443f4a40b 100644 --- a/plugins/main/public/components/common/modules/modules-helper.js +++ b/plugins/main/public/components/common/modules/modules-helper.js @@ -1,6 +1,7 @@ import { getAngularModule, getDataPlugin } from '../../../kibana-services'; import { AppState } from '../../../react-services/app-state'; import { FilterHandler } from '../../../utils/filter-handler'; +import { VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER } from '../../../../common/constants'; export class ModulesHelper { static async getDiscoverScope() { @@ -157,6 +158,18 @@ If this case happens, the implicit filters are regenerated and the function is c isCluster, ), ); + /* Add vulnerability cluster implicit filter*/ + filters.push( + filterHandler.managerQuery( + isCluster + ? AppState.getClusterInfo().cluster + : AppState.getClusterInfo().manager, + isCluster, + VULNERABILITY_IMPLICIT_CLUSTER_MODE_FILTER[ + AppState.getClusterInfo().status + ], + ), + ); filters.push(filterHandler.pciQuery()); filters.push(filterHandler.gdprQuery()); filters.push(filterHandler.hipaaQuery()); diff --git a/plugins/main/public/components/common/permissions/button.tsx b/plugins/main/public/components/common/permissions/button.tsx index 799f5539ae..419477fc2e 100644 --- a/plugins/main/public/components/common/permissions/button.tsx +++ b/plugins/main/public/components/common/permissions/button.tsx @@ -34,7 +34,7 @@ interface IWzButtonPermissionsProps export const WzButtonPermissions = ({ buttonType = 'default', permissions, - roles, + administrator, tooltip, ...rest }: IWzButtonPermissionsProps) => { @@ -52,7 +52,7 @@ export const WzButtonPermissions = ({ return ( { const additionalProps = { diff --git a/plugins/main/public/components/common/permissions/element.tsx b/plugins/main/public/components/common/permissions/element.tsx index 216a9f7d44..d957a82482 100644 --- a/plugins/main/public/components/common/permissions/element.tsx +++ b/plugins/main/public/components/common/permissions/element.tsx @@ -12,11 +12,9 @@ import React, { Fragment } from 'react'; import { useUserPermissionsRequirements } from '../hooks/useUserPermissions'; -import { useUserRolesRequirements } from '../hooks/useUserRoles'; - import { EuiToolTip, EuiSpacer } from '@elastic/eui'; - import { WzPermissionsFormatted } from './format'; +import { useUserPermissionsIsAdminRequirements } from '../hooks/use-user-is-admin'; export interface IUserPermissionsObject { action: string; @@ -25,11 +23,12 @@ export interface IUserPermissionsObject { export type TUserPermissionsFunction = (props: any) => TUserPermissions; export type TUserPermissions = (string | IUserPermissionsObject)[] | null; export type TUserRoles = string[] | null; +export type TUserIsAdministrator = string | null; export type TUserRolesFunction = (props: any) => TUserRoles; export interface IWzElementPermissionsProps { permissions?: TUserPermissions | TUserPermissionsFunction; - roles?: TUserRoles | TUserRolesFunction; + administrator?: boolean; tooltip?: any; children: React.ReactElement; getAdditionalProps?: (disabled: boolean) => { @@ -40,7 +39,7 @@ export interface IWzElementPermissionsProps { export const WzElementPermissions = ({ children, permissions = null, - roles = null, + administrator = false, getAdditionalProps, tooltip, ...rest @@ -48,15 +47,16 @@ export const WzElementPermissions = ({ const [userPermissionRequirements] = useUserPermissionsRequirements( typeof permissions === 'function' ? permissions(rest) : permissions, ); - const [userRolesRequirements] = useUserRolesRequirements( - typeof roles === 'function' ? roles(rest) : roles, - ); - const isDisabledByRolesOrPermissions = - userRolesRequirements || userPermissionRequirements; + const [userRequireAdministratorRequirements] = + useUserPermissionsIsAdminRequirements(); + + const isDisabledByPermissions = + userPermissionRequirements || + (administrator && userRequireAdministratorRequirements); const disabled = Boolean( - isDisabledByRolesOrPermissions || rest?.isDisabled || rest?.disabled, + isDisabledByPermissions || rest?.isDisabled || rest?.disabled, ); const additionalProps = getAdditionalProps @@ -67,7 +67,7 @@ export const WzElementPermissions = ({ ...additionalProps, }); - const contentTextRequirements = isDisabledByRolesOrPermissions && ( + const contentTextRequirements = isDisabledByPermissions && ( {userPermissionRequirements && (
@@ -81,23 +81,18 @@ export const WzElementPermissions = ({ {WzPermissionsFormatted(userPermissionRequirements)}
)} - {userPermissionRequirements && userRolesRequirements && ( + {userPermissionRequirements && userRequireAdministratorRequirements && ( )} - {userRolesRequirements && ( + {userRequireAdministratorRequirements && (
- Require{' '} - {userRolesRequirements - .map(role => ( - {role} - )) - .reduce((prev, cur) => [prev, ', ', cur])}{' '} - {userRolesRequirements.length > 1 ? 'roles' : 'role'} + Require administrator privilegies:{' '} + {userRequireAdministratorRequirements}
)}
); - return isDisabledByRolesOrPermissions ? ( + return isDisabledByPermissions ? ( {childrenWithAdditionalProps} diff --git a/plugins/main/public/components/common/permissions/prompt.tsx b/plugins/main/public/components/common/permissions/prompt.tsx index ae7817dd5a..962cdcd491 100644 --- a/plugins/main/public/components/common/permissions/prompt.tsx +++ b/plugins/main/public/components/common/permissions/prompt.tsx @@ -11,44 +11,38 @@ */ import React, { Fragment } from 'react'; -import { useUserPermissionsRequirements, useUserRolesRequirements } from '../hooks'; import { EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; -import { - TUserPermissions, - TUserPermissionsFunction, - TUserRoles, - TUserRolesFunction, -} from '../permissions/element'; +import { TUserPermissions, TUserIsAdministrator } from '../permissions/element'; import { WzPermissionsFormatted } from './format'; import { withErrorBoundary } from '../hocs/error-boundary/with-error-boundary'; interface IEmptyPromptNoPermissions { permissions?: TUserPermissions; - roles?: TUserRoles; + administrator?: TUserIsAdministrator; actions?: React.ReactNode; } export const WzEmptyPromptNoPermissions = withErrorBoundary( - ({ permissions, roles, actions }: IEmptyPromptNoPermissions) => { + ({ permissions, administrator, actions }: IEmptyPromptNoPermissions) => { return ( You have no permissions} body={ {permissions && (
- This section requires the {permissions.length > 1 ? 'permissions' : 'permission'}: + This section requires the{' '} + {permissions.length > 1 ? 'permissions' : 'permission'}: {WzPermissionsFormatted(permissions)}
)} - {permissions && roles && } - {roles && ( + {permissions && administrator && } + {administrator && (
- This section requires{' '} - {roles - .map((role) => {role}) - .reduce((accum, cur) => [accum, ', ', cur])}{' '} - {roles.length > 1 ? 'roles' : 'role'} + This section requires administrator privilegies:{' '} + + {administrator} +
)}
@@ -56,34 +50,5 @@ export const WzEmptyPromptNoPermissions = withErrorBoundary( actions={actions} /> ); - } + }, ); -interface IPromptNoPermissions { - permissions?: TUserPermissions | TUserPermissionsFunction; - roles?: TUserRoles | TUserRolesFunction; - children?: React.ReactNode; - rest?: any; -} - -export const WzPromptPermissions = ({ - permissions = null, - roles = null, - children, - ...rest -}: IPromptNoPermissions) => { - const [userPermissionRequirements, userPermissions] = useUserPermissionsRequirements( - typeof permissions === 'function' ? permissions(rest) : permissions - ); - const [userRolesRequirements, userRoles] = useUserRolesRequirements( - typeof roles === 'function' ? roles(rest) : roles - ); - - return userPermissionRequirements || userRolesRequirements ? ( - - ) : ( - children - ); -}; diff --git a/plugins/main/public/components/common/search-bar/index.ts b/plugins/main/public/components/common/search-bar/index.ts index 104abb8280..9ed04b634b 100644 --- a/plugins/main/public/components/common/search-bar/index.ts +++ b/plugins/main/public/components/common/search-bar/index.ts @@ -1,2 +1,2 @@ export * from './search-bar-service'; -export * from './use-search-bar'; \ No newline at end of file +export * from './use-search-bar'; diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts index 82c1caccf7..23329515ad 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.test.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts @@ -161,6 +161,7 @@ describe('[hook] useSearchBarConfiguration', () => { }); it('should return the same filters and apply them to the filter manager when are received by props', async () => { + const exampleIndexPatternId = 'wazuh-index-pattern'; const defaultFilters: Filter[] = [ { query: 'something to filter', @@ -183,6 +184,7 @@ describe('[hook] useSearchBarConfiguration', () => { .mockReturnValue(defaultFilters); const { result, waitForNextUpdate } = renderHook(() => useSearchBar({ + defaultIndexPatternID: exampleIndexPatternId, filters: defaultFilters, }), ); diff --git a/plugins/main/public/components/common/search-bar/use-search-bar.ts b/plugins/main/public/components/common/search-bar/use-search-bar.ts index 40a7488210..e5cf2b1999 100644 --- a/plugins/main/public/components/common/search-bar/use-search-bar.ts +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -8,12 +8,12 @@ import { IIndexPattern, IndexPatternsContract, } from '../../../../../../src/plugins/data/public'; -import { getDataPlugin } from '../../../kibana-services'; +import { getDataPlugin, getWazuhCorePlugin } from '../../../kibana-services'; import { useFilterManager, useQueryManager, useTimeFilter } from '../hooks'; -import { AUTHORIZED_AGENTS } from '../../../../common/constants'; -import { AppState } from '../../../react-services/app-state'; -import { getSettingDefaultValue } from '../../../../common/services/settings'; -import { FilterStateStore } from '../../../../../../src/plugins/data/common'; +import { + AUTHORIZED_AGENTS, + DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, +} from '../../../../common/constants'; // Input - types type tUseSearchBarCustomInputs = { @@ -23,6 +23,17 @@ type tUseSearchBarCustomInputs = { payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean, ) => void; + onMount?: ( + filterManager: FilterManager, + defaultIndexPatternID: string, + ) => void; + onUpdate?: (filters: Filter[], filterManager: FilterManager) => void; + onUnMount?: ( + previousFilters: Filter[], + toIndexPattern: string | null, + filterManager: FilterManager, + defaultIndexPatternID: string, + ) => void; }; type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; @@ -36,12 +47,14 @@ type tUserSearchBarResponse = { * @param props * @returns */ -const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { +const useSearchBarConfiguration = ( + props?: tUseSearchBarProps, +): tUserSearchBarResponse => { // dependencies const SESSION_STORAGE_FILTERS_NAME = 'wazuh_persistent_searchbar_filters'; const SESSION_STORAGE_PREV_FILTER_NAME = 'wazuh_persistent_current_filter'; const filterManager = useFilterManager().filterManager as FilterManager; - const { filters } = useFilterManager(); + const filters = props?.filters ? props.filters : filterManager.getFilters(); const [query, setQuery] = props?.query ? useState(props?.query) : useQueryManager(); @@ -52,43 +65,26 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { useState(); useEffect(() => { - const prevPattern = - AppState.getCurrentPattern() || getSettingDefaultValue('pattern'); - if (filters && filters.length > 0) { - sessionStorage.setItem( - SESSION_STORAGE_FILTERS_NAME, - JSON.stringify( - updatePrevFilters(filters, props?.defaultIndexPatternID), - ), - ); - } - sessionStorage.setItem(SESSION_STORAGE_PREV_FILTER_NAME, prevPattern); - AppState.setCurrentPattern(props?.defaultIndexPatternID); initSearchBar(); - - /** - * When the component is unmounted, the original filters that arrived - * when the component was mounted are added. - * Both when the component is mounted and unmounted, the index pattern is - * updated so that the pin action adds the agent with the correct index pattern. - */ return () => { + /* Upon unmount, the previous filters are restored */ const prevStoragePattern = sessionStorage.getItem( SESSION_STORAGE_PREV_FILTER_NAME, ); - AppState.setCurrentPattern(prevStoragePattern); sessionStorage.removeItem(SESSION_STORAGE_PREV_FILTER_NAME); const storagePreviousFilters = sessionStorage.getItem( SESSION_STORAGE_FILTERS_NAME, ); if (storagePreviousFilters) { const previousFilters = JSON.parse(storagePreviousFilters); - const cleanedFilters = cleanFilters( - previousFilters, - prevStoragePattern ?? prevPattern, - ); - filterManager.setFilters(cleanedFilters); - sessionStorage.removeItem(SESSION_STORAGE_FILTERS_NAME); + if (props?.onUnMount) { + props.onUnMount( + previousFilters, + prevStoragePattern, + filterManager, + props?.defaultIndexPatternID, + ); + } } }; }, []); @@ -100,8 +96,11 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { setIsLoading(true); const indexPattern = await getIndexPattern(props?.defaultIndexPatternID); setIndexPatternSelected(indexPattern); - const initialFilters = props?.filters ?? filters; - filterManager.setFilters(initialFilters); + if (props?.onMount) { + props.onMount(filterManager, props?.defaultIndexPatternID); + } else { + filterManager.setFilters(filters); + } setIsLoading(false); }; @@ -126,199 +125,34 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { } }; - const updatePrevFilters = ( - previousFilters: Filter[], - indexPattern?: string, - ) => { - const pinnedAgent = previousFilters.find( - (filter: Filter) => - filter.meta.key === 'agent.id' && !!filter?.$state?.isImplicit, - ); - if (!pinnedAgent) { - const url = window.location.href; - const regex = new RegExp('agentId=' + '[^&]*'); - const match = url.match(regex); - if (match && match[0]) { - const agentId = match[0].split('=')[1]; - const agentFilters = previousFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - const insertPinnedAgent = { - meta: { - alias: null, - disabled: false, - key: 'agent.id', - negate: false, - params: { query: agentId }, - type: 'phrase', - index: indexPattern, - }, - query: { - match: { - 'agent.id': { - query: agentId, - type: 'phrase', - }, - }, - }, - $state: { store: 'appState', isImplicit: true }, - }; - agentFilters.push(insertPinnedAgent); - return agentFilters; - } - } - if (pinnedAgent) { - const agentFilters = previousFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - agentFilters.push({ - ...pinnedAgent, - meta: { - ...pinnedAgent.meta, - index: indexPattern, - }, - $state: { store: FilterStateStore.APP_STATE, isImplicit: true }, - }); - return agentFilters; - } - return previousFilters; - }; - - /** - * Return filters from filters manager. - * Additionally solve the known issue with the auto loaded agent.id filters from the searchbar - * and filters those filters that are not related to the default index pattern - * @returns - */ - const getFilters = () => { - const originalFilters = filterManager ? filterManager.getFilters() : []; - const pinnedAgent = originalFilters.find( - (filter: Filter) => - filter.meta.key === 'agent.id' && !!filter?.$state?.isImplicit, - ); - const mappedFilters = originalFilters.filter( - (filter: Filter) => - filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && // remove auto loaded agent.id filters - filter?.meta?.index === props?.defaultIndexPatternID, - ); - - if (pinnedAgent) { - const agentFilters = mappedFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - agentFilters.push({ - ...pinnedAgent, - meta: { - ...pinnedAgent.meta, - index: props?.defaultIndexPatternID, - }, - }); - return agentFilters; - } - return mappedFilters; - }; - - /** - * Return cleaned filters. - * Clean the known issue with the auto loaded agent.id filters from the searchbar - * and filters those filters that are not related to the default index pattern. - * This cleanup adjusts the index pattern of a pinned agent, if applicable. - * @param previousFilters - * @returns - */ - const cleanFilters = (previousFilters: Filter[], indexPattern?: string) => { - /** - * Verify if a pinned agent exists, identifying it by its meta.isImplicit attribute or by the agentId query param URL. - * We also compare the agent.id filter with the agentId query param because the OSD filter definition does not include the "isImplicit" attribute that Wazuh adds. - * There may be cases where the "isImplicit" attribute is lost, since any action regarding filters that is done with the - * filterManager ( addFilters, setFilters, setGlobalFilters, setAppFilters) - * does a mapAndFlattenFilters mapping to the filters that removes any attributes that are not part of the filter definition. - * */ - const mappedFilters = previousFilters.filter( - (filter: Filter) => - filter?.meta?.controlledBy !== AUTHORIZED_AGENTS && // remove auto loaded agent.id filters - filter?.meta?.index !== props?.defaultIndexPatternID, - ); - const pinnedAgent = mappedFilters.find( - (filter: Filter) => - filter.meta.key === 'agent.id' && !!filter?.$state?.isImplicit, - ); - - if (!pinnedAgent) { - const url = window.location.href; - const regex = new RegExp('agentId=' + '[^&]*'); - const match = url.match(regex); - if (match && match[0]) { - const agentId = match[0].split('=')[1]; - const agentFilters = mappedFilters.filter(x => { - return x.meta.key !== 'agent.id'; - }); - const insertPinnedAgent = { - meta: { - alias: null, - disabled: false, - key: 'agent.id', - negate: false, - params: { query: agentId }, - type: 'phrase', - index: indexPattern, - }, - query: { - match: { - 'agent.id': { - query: agentId, - type: 'phrase', - }, - }, - }, - $state: { store: 'appState', isImplicit: true }, - }; - agentFilters.push(insertPinnedAgent); - return agentFilters; - } - } - if (pinnedAgent) { - mappedFilters.push({ - ...pinnedAgent, - meta: { - ...pinnedAgent.meta, - index: indexPattern, - }, - $state: { store: FilterStateStore.APP_STATE, isImplicit: true }, - }); - } - return mappedFilters; - }; - /** * Search bar properties necessary to render and initialize the osd search bar component */ const searchBarProps: Partial = { isLoading, ...(indexPatternSelected && { indexPatterns: [indexPatternSelected] }), // indexPattern cannot be empty or empty [] - filters: getFilters(), + filters: filters + .filter( + (filter: Filter) => + ![ + AUTHORIZED_AGENTS, + DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, + ].includes(filter?.meta?.controlledBy), // remove auto loaded agent.id filters + ) + .sort((a: Filter, b: Filter) => { + return a?.$state?.isImplicit && !(a?.meta?.key === 'agent.id') + ? -1 + : b?.$state?.isImplicit + ? 1 + : -1; + }), query, timeHistory, dateRangeFrom: timeFilter.from, dateRangeTo: timeFilter.to, onFiltersUpdated: (filters: Filter[]) => { - const storagePreviousFilters = sessionStorage.getItem( - SESSION_STORAGE_FILTERS_NAME, - ); - /** - * If there are persisted filters, it is necessary to add them when - * updating the filters in the filterManager - */ - if (storagePreviousFilters) { - const previousFilters = JSON.parse(storagePreviousFilters); - const cleanedFilters = cleanFilters( - previousFilters, - props?.defaultIndexPatternID, - ); - filterManager.setFilters([...cleanedFilters, ...filters]); - - props?.onFiltersUpdated && - props?.onFiltersUpdated([...cleanedFilters, ...filters]); + if (props?.onUpdate) { + props.onUpdate(filters, filterManager, props?.onFiltersUpdated); } else { filterManager.setFilters(filters); props?.onFiltersUpdated && props?.onFiltersUpdated(filters); @@ -343,4 +177,4 @@ const useSearchBar = (props?: tUseSearchBarProps): tUserSearchBarResponse => { }; }; -export default useSearchBar; +export default useSearchBarConfiguration; diff --git a/plugins/main/public/components/common/tables/__snapshots__/table-default.test.tsx.snap b/plugins/main/public/components/common/tables/__snapshots__/table-default.test.tsx.snap index c5da18140d..fd8283c553 100644 --- a/plugins/main/public/components/common/tables/__snapshots__/table-default.test.tsx.snap +++ b/plugins/main/public/components/common/tables/__snapshots__/table-default.test.tsx.snap @@ -226,7 +226,7 @@ exports[`Table Default component renders correctly to match the snapshot 1`] = ` size="s" type="arrowDown" > - - + > + + + - - + > + + +
- -
- -

- Table - - - - -

-
-
-
- -
- -
- - - -
-
- -
- +
+ +
+ +
+ +

+ Table + + + + +

+
+
+
+
+
+
+ +
-
- - + +
+
+
+
+
+ + +
+ + +
+
+
-
- - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="none" +
+ + +
-
- - - -
+ + + + + + + + + + + + Sorting + + + + + + +
+
+
+
+
- +
- +
- -
- -
- - - - - - - - - - + +
- -
- + + + + + - - + - - - - + + - - + - - - - + + - - + - - - - + + - - + + + + + + + + + + + + - - - - - - - - - - - - - -
-
- - +
- + - + - +
- - Name - - - - - - - - -
-
- - No items found - -
-
-
+ + No items found + +
+ + + + + + + + +
+
+ +
-
- - + +
+ `; diff --git a/plugins/main/public/components/common/tables/components/__snapshots__/export-table-csv.test.tsx.snap b/plugins/main/public/components/common/tables/components/__snapshots__/export-table-csv.test.tsx.snap index a0061544cc..7db15c6903 100644 --- a/plugins/main/public/components/common/tables/components/__snapshots__/export-table-csv.test.tsx.snap +++ b/plugins/main/public/components/common/tables/components/__snapshots__/export-table-csv.test.tsx.snap @@ -44,7 +44,7 @@ exports[`Export Table Csv component renders correctly to match the snapshot 1`] size="m" type="importAction" > - - + > + + + ({ const [refresh, setRefresh] = useState(Date.now()); const isMounted = useRef(false); + const tableRef = useRef(); const searchBarWQLOptions = useMemo( () => ({ @@ -177,6 +178,10 @@ export function TableWithSearchBar({ (async () => { try { setLoading(true); + + //Reset the table selection in case is enabled + tableRef.current.setSelection([]); + const { items, totalItems } = await onSearch( endpoint, filters, @@ -254,6 +259,7 @@ export function TableWithSearchBar({ /> ({ ...rest }), )} diff --git a/plugins/main/public/components/common/tables/table-wz-api.tsx b/plugins/main/public/components/common/tables/table-wz-api.tsx index fc11c05c42..42821434de 100644 --- a/plugins/main/public/components/common/tables/table-wz-api.tsx +++ b/plugins/main/public/components/common/tables/table-wz-api.tsx @@ -18,7 +18,6 @@ import { EuiFlexItem, EuiText, EuiButtonEmpty, - EuiSpacer, EuiToolTip, EuiIcon, EuiCheckboxGroup, @@ -27,9 +26,6 @@ import { TableWithSearchBar } from './table-with-search-bar'; import { TableDefault } from './table-default'; import { WzRequest } from '../../../react-services/wz-request'; import { ExportTableCsv } from './components/export-table-csv'; -import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; -import { UI_LOGGER_LEVELS } from '../../../../common/constants'; -import { getErrorOrchestrator } from '../../../react-services/common-services'; import { useStateStorage } from '../hooks'; /** @@ -50,8 +46,12 @@ export function TableWzAPI({ actionButtons, ...rest }: { - actionButtons?: ReactNode | ReactNode[]; + actionButtons?: + | ReactNode + | ReactNode[] + | (({ filters }: { filters }) => ReactNode); title?: string; + addOnTitle?: ReactNode; description?: string; downloadCsv?: boolean | string; searchTable?: boolean; @@ -61,15 +61,21 @@ export function TableWzAPI({ showReload?: boolean; searchBarProps?: any; reload?: boolean; + onDataChange?: Function; + setReload?: (newValue: number) => void; }) { const [totalItems, setTotalItems] = useState(0); const [filters, setFilters] = useState({}); const [isLoading, setIsLoading] = useState(false); + const onFiltersChange = filters => typeof rest.onFiltersChange === 'function' ? rest.onFiltersChange(filters) : null; + const onDataChange = data => + typeof rest.onDataChange === 'function' ? rest.onDataChange(data) : null; + /** * Changing the reloadFootprint timestamp will trigger reloading the table */ @@ -112,10 +118,15 @@ export function TableWzAPI({ ).data; setIsLoading(false); setTotalItems(totalItems); - return { + + const result = { items: rest.mapResponseItem ? items.map(rest.mapResponseItem) : items, totalItems, }; + + onDataChange(result); + + return result; } catch (error) { setIsLoading(false); setTotalItems(0); @@ -132,25 +143,32 @@ export function TableWzAPI({ }, []); - const renderActionButtons = ( - <> - {Array.isArray(actionButtons) - ? actionButtons.map((button, key) => ( - - {button} - - )) - : typeof actionButtons === 'object' && ( - {actionButtons} - )} - - ); + const renderActionButtons = filters => { + if (Array.isArray(actionButtons)) { + return actionButtons.map((button, key) => ( + + {button} + + )); + } + + if (typeof actionButtons === 'object') { + return {actionButtons}; + } + + if (typeof actionButtons === 'function') { + return actionButtons({ filters: getFilters(filters) }); + } + }; /** - * Generate a new reload footprint + * Generate a new reload footprint and set reload to propagate refresh */ const triggerReload = () => { setReloadFootprint(Date.now()); + if (rest.setReload) { + rest.setReload(Date.now()); + } }; useEffect(() => { @@ -167,28 +185,34 @@ export function TableWzAPI({ const header = ( <> - - - {rest.title && ( - -

- {rest.title}{' '} - {isLoading ? ( - - ) : ( - ({totalItems}) - )} -

-
- )} - {rest.description && ( - {rest.description} - )} -
+ - + + + {rest.title && ( + +

+ {rest.title}{' '} + {isLoading ? ( + + ) : ( + ({totalItems}) + )} +

+
+ )} +
+ {rest.addOnTitle ? ( + + {rest.addOnTitle} + + ) : null} +
+
+ + {/* Render optional custom action button */} - {renderActionButtons} + {renderActionButtons(filters)} {/* Render optional reload button */} {rest.showReload && ReloadButton} {/* Render optional export to CSV button */} @@ -266,11 +290,15 @@ export function TableWzAPI({ ); return ( - <> - {header} - {rest.description && } - {table} - + + {header} + {rest.description && ( + + {rest.description} + + )} + {table} + ); } diff --git a/plugins/main/public/components/endpoints-summary/dashboard/components/donut-card.test.tsx b/plugins/main/public/components/endpoints-summary/dashboard/components/donut-card.test.tsx new file mode 100644 index 0000000000..7f7b40da9a --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/dashboard/components/donut-card.test.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { render, act, fireEvent } from '@testing-library/react'; +import DonutCard from './donut-card'; +import '@testing-library/jest-dom/extend-expect'; + +/* It is necessary to mock the ResizeObserver class because it is used in the useChartDimensions hook in one of the DonutChart subcomponents */ +class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +} +global.ResizeObserver = ResizeObserver; + +jest.mock('../../../common/hooks/use-service', () => ({ + __esModule: true, + useService: jest.fn(), +})); + +describe('DonutCard', () => { + const mockLoading = false; + const mockData = [ + { + status: 'active', + label: 'Active', + value: 1, + color: '#007871', + }, + { + status: 'disconnected', + label: 'Disconnected', + value: 0, + color: '#BD271E', + }, + { + status: 'pending', + label: 'Pending', + value: 0, + color: '#FEC514', + }, + { + status: 'never_connected', + label: 'Never connected', + value: 0, + color: '#646A77', + }, + ]; + const mockGetInfo = jest.fn().mockResolvedValue(mockData); + const useServiceMock = jest.fn(() => ({data: mockData, isLoading: mockLoading})); + const mockGetInfoNoData = jest.fn().mockResolvedValue([]); + const useServiceMockNoData = jest.fn(() => ({data: [], isLoading: mockLoading})); + + it('renders with data', async () => { + require('../../../common/hooks/use-service').useService = useServiceMock; + + await act(async () => { + const { getByText } = render( + , + ); + + expect(getByText('Component title example')).toBeInTheDocument(); + expect(getByText('Component description example')).toBeInTheDocument(); + expect(getByText('Component betaBadgeLabel example')).toBeInTheDocument(); + mockData.forEach(element => { + expect(getByText(`${element.label} (${element.value})`)).toBeInTheDocument(); + }); + }); + }); + + it('handles click on data', async () => { + require('../../../common/hooks/use-service').useService = useServiceMock; + + const handleClick = jest.fn(); + const firstMockData = mockData[0]; + + await act(async () => { + const { getByText } = render( + + ); + + fireEvent.click(getByText(`${firstMockData.label} (${firstMockData.value})`)); + + expect(handleClick).toHaveBeenCalledTimes(1); + + expect(handleClick).toHaveBeenCalledWith(firstMockData); + }); + }); + + it('show noDataTitle and noDataMessage when no data', async () => { + require('../../../common/hooks/use-service').useService = useServiceMockNoData; + + await act(async () => { + const { getByText } = render( + + ); + + expect(getByText('Component no data title example message')).toBeInTheDocument(); + expect(getByText('Component no data example message')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/dashboard/components/donut-card.tsx b/plugins/main/public/components/endpoints-summary/dashboard/components/donut-card.tsx new file mode 100644 index 0000000000..58acf75385 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/dashboard/components/donut-card.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiCard } from '@elastic/eui'; +import { VisualizationBasic } from '../../../common/charts/visualizations/basic'; +import { useService } from '../../../common/hooks/use-service'; + +interface AgentsByStatusCardProps { + title?: string; + description?: string; + betaBadgeLabel?: string; + noDataTitle?: string; + noDataMessage?: string; + reload?: number; + getInfo: () => Promise; + onClickLabel?: (status: any) => void; + [key: string]: any; +} + +const DonutCard = ({ + title = '', + description = '', + betaBadgeLabel, + noDataTitle = 'No results', + noDataMessage = 'No results were found', + reload, + getInfo, + onClickLabel, + ...props +}: AgentsByStatusCardProps) => { + const { data, isLoading } = useService(getInfo, undefined, reload); + + const handleClick = (item: any) => { + if (onClickLabel) { + onClickLabel(item); + } + }; + + return ( + + + + ({ + ...item, + onClick: () => handleClick(item), + }))} + noDataTitle={noDataTitle} + noDataMessage={noDataMessage} + /> + + + + ); +}; + +export default DonutCard; diff --git a/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.scss b/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.scss new file mode 100644 index 0000000000..65ab2e1b83 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.scss @@ -0,0 +1,22 @@ +.wazuh-outdated-agents-panel { + display: flex; + align-items: center; + justify-content: center; + margin: auto; + flex-direction: column; + cursor: pointer; +} + +.wazuh-outdated-metric .euiTitle { + font-size: 4.5rem; + line-height: inherit; +} + +.wazuh-outdated-metric small { + font-size: 1.5rem; +} + +.wazuh-outdated-icon { + width: 3.5rem; + height: 3.5rem; +} diff --git a/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.test.tsx b/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.test.tsx new file mode 100644 index 0000000000..7929b1473a --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.test.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { render, act } from '@testing-library/react'; +import OutdatedAgentsCard from './outdated-agents-card'; +import '@testing-library/jest-dom/extend-expect'; +import { mount } from 'enzyme'; +import { EuiButtonEmpty, EuiLink } from '@elastic/eui'; +import { webDocumentationLink } from '../../../../../common/services/web_documentation'; + +jest.mock('../../../common/hooks/use-service', () => ({ + __esModule: true, + useService: jest.fn(), +})); + +describe('OutdatedAgentsCard', () => { + const awaitForMyComponent = async (wrapper: any) => { + await act(async () => { + await new Promise(resolve => setTimeout(resolve, 0)); + wrapper.update(); + }); + }; + + const mockLoading = false; + const mockDataNoOutdatedAgents = []; + const useServiceMockNoOutdatedAgent = jest.fn(() => ({data: mockDataNoOutdatedAgents, isLoading: mockLoading})); + const mockDataOutdatedAgents = [ + { + version: "Wazuh v3.0.0", + id: "003", + name: "main_database" + }, + { + version: "Wazuh v3.0.0", + id: "004", + name: "dmz002" + } +]; + const useServiceMockOutdatedAgent = jest.fn(() => ({data: mockDataOutdatedAgents, isLoading: mockLoading})); + + const handleClick = jest.fn(); + + it('renders with not outdated agents', async () => { + require('../../../common/hooks/use-service').useService = useServiceMockNoOutdatedAgent; + + await act(async () => { + const { getByTestId } = render( + + ); + + const outdatedAgentsNumberElement = getByTestId('wazuh-endpoints-summary-outdated-agents-number') + expect(outdatedAgentsNumberElement).toHaveClass('euiTextColor euiTextColor--success'); + expect(outdatedAgentsNumberElement.textContent).toBe(`${mockDataNoOutdatedAgents.length}`); + }); + }); + + it('renders with outdated agents', async () => { + require('../../../common/hooks/use-service').useService = useServiceMockOutdatedAgent; + + await act(async () => { + const { getByTestId } = render( + + ); + + const outdatedAgentsNumberElement = getByTestId('wazuh-endpoints-summary-outdated-agents-number') + expect(outdatedAgentsNumberElement).toHaveClass('euiTextColor euiTextColor--warning'); + expect(outdatedAgentsNumberElement.textContent).toBe(`${mockDataOutdatedAgents.length}`); + }); + }); + + it('renders popover on click with outdated agents', async () => { + require('../../../common/hooks/use-service').useService = useServiceMockOutdatedAgent; + + const wrapper = await mount( + , + ); + + await awaitForMyComponent(wrapper); + + expect(wrapper.find('.wazuh-outdated-agents-panel').exists()).toBeTruthy(); + expect(wrapper.find(EuiButtonEmpty).exists()).not.toBeTruthy(); + + wrapper.find('.wazuh-outdated-agents-panel').simulate('click'); + expect(wrapper.find(EuiButtonEmpty).exists()).toBeTruthy(); + }); + + it('handles click with correct data', async () => { + require('../../../common/hooks/use-service').useService = useServiceMockOutdatedAgent; + + const wrapper = await mount( + , + ); + + await awaitForMyComponent(wrapper); + + expect(wrapper.find('.wazuh-outdated-agents-panel').exists()).toBeTruthy(); + expect(wrapper.find(EuiButtonEmpty).exists()).not.toBeTruthy(); + + wrapper.find('.wazuh-outdated-agents-panel').simulate('click'); + expect(wrapper.find(EuiButtonEmpty).exists()).toBeTruthy(); + + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(handleClick).toHaveBeenCalledTimes(1); + expect(handleClick).toHaveBeenCalledWith(mockDataOutdatedAgents); + }); + + it('EuiButtonEmpty filter must be disabled when no data', async () => { + require('../../../common/hooks/use-service').useService = useServiceMockNoOutdatedAgent; + + const wrapper = await mount( + , + ); + + await awaitForMyComponent(wrapper); + + expect(wrapper.find('.wazuh-outdated-agents-panel').exists()).toBeTruthy(); + expect(wrapper.find(EuiButtonEmpty).exists()).not.toBeTruthy(); + + wrapper.find('.wazuh-outdated-agents-panel').simulate('click'); + expect(wrapper.find(EuiButtonEmpty).exists()).toBeTruthy(); + expect(wrapper.find(EuiButtonEmpty).prop('isDisabled')).toBe(true); + }); + + it('check documentation link to update agents', async () => { + const documentationLink = webDocumentationLink( + 'upgrade-guide/wazuh-agent/index.html', + ); + require('../../../common/hooks/use-service').useService = useServiceMockNoOutdatedAgent; + + const wrapper = await mount( + , + ); + + await awaitForMyComponent(wrapper); + + expect(wrapper.find('.wazuh-outdated-agents-panel').exists()).toBeTruthy(); + expect(wrapper.find(EuiButtonEmpty).exists()).not.toBeTruthy(); + + wrapper.find('.wazuh-outdated-agents-panel').simulate('click'); + expect(wrapper.find(EuiLink).exists()).toBeTruthy(); + expect(wrapper.find(EuiLink).prop('href')).toBe(documentationLink); + }); + +}); diff --git a/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.tsx b/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.tsx new file mode 100644 index 0000000000..304a05a8ab --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/dashboard/components/outdated-agents-card.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { + EuiFlexItem, + EuiCard, + EuiIcon, + EuiStat, + EuiTextColor, + EuiPopover, + EuiPopoverFooter, + EuiLink, + EuiButtonEmpty, +} from '@elastic/eui'; +import './outdated-agents-card.scss'; +import { getOutdatedAgents } from '../../services/get-outdated-agents'; +import { webDocumentationLink } from '../../../../../common/services/web_documentation'; +import { useService } from '../../../common/hooks/use-service'; + +interface OutdatedAgentsCardProps { + onClick?: (status: any) => void; + reload?: number; + [key: string]: any; +} + +const OutdatedAgentsCard = ({ + onClick, + reload, + ...props +}: OutdatedAgentsCardProps) => { + const { data, isLoading } = useService( + getOutdatedAgents, + undefined, + reload, + ); + const outdatedAgents = data?.length; + const contentType = outdatedAgents > 0 ? 'warning' : 'success'; + const contentIcon = outdatedAgents > 0 ? 'alert' : 'check'; + const [showOutdatedAgents, setShowOutdatedAgents] = + React.useState(false); + + const onShowOutdatedAgents = () => setShowOutdatedAgents(!showOutdatedAgents); + const onHideOutdatedAgents = () => setShowOutdatedAgents(false); + + const handleClick = () => { + if (onClick) { + onClick(data); + setShowOutdatedAgents(false); + } + }; + + const renderMetric = () => { + return ( +
+ + + {outdatedAgents} + + } + description={ + + Agents + + } + titleColor='danger' + isLoading={isLoading} + titleSize='l' + textAlign='center' + reverse + /> +
+ ); + }; + + return ( + + + + 0)} + > + Filter outdated agents + + + + + How to update agents + + + + + + + ); +}; + +export default OutdatedAgentsCard; diff --git a/plugins/main/public/components/endpoints-summary/dashboard/endpoints-summary-dashboard.tsx b/plugins/main/public/components/endpoints-summary/dashboard/endpoints-summary-dashboard.tsx new file mode 100644 index 0000000000..5e74dc9842 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/dashboard/endpoints-summary-dashboard.tsx @@ -0,0 +1,49 @@ +import React, { FC } from 'react'; +import { getAgentsByGroup } from '../services/get-agents-by-group'; +import { getAgentsByOs } from '../services/get-agents-by-os'; +import { getSummaryAgentsStatus } from '../services/get-summary-agents-status'; +import DonutCard from './components/donut-card'; +import OutdatedAgentsCard from './components/outdated-agents-card'; + +interface EndpointsSummaryDashboardProps { + filterAgentByStatus: (data: any) => void; + filterAgentByOS: (data: any) => void; + filterAgentByGroup: (data: any) => void; + filterByOutdatedAgent: (data: any) => void; + reloadDashboard?: number; +} + +export const EndpointsSummaryDashboard: FC = ({ + filterAgentByStatus, + filterAgentByOS, + filterAgentByGroup, + filterByOutdatedAgent, + reloadDashboard, +}) => { + return ( +
+ + + + +
+ ); +}; diff --git a/plugins/main/public/components/endpoints-summary/endpoints-summary.scss b/plugins/main/public/components/endpoints-summary/endpoints-summary.scss index e420ca4e33..f9e50aef92 100644 --- a/plugins/main/public/components/endpoints-summary/endpoints-summary.scss +++ b/plugins/main/public/components/endpoints-summary/endpoints-summary.scss @@ -101,3 +101,22 @@ white-space: nowrap; } } + +.endpoints-summary-container-indicators { + width: 100%; + display: grid; + grid-template-columns: 1fr; + gap: 20px 10px; + min-height: 200px; + + @media (min-width: 1024px) { + grid-template-columns: 1fr 1fr; + } + + @media (min-width: 1440px) { + gap: 10px; + grid-template-columns: + minmax(375px, 1fr) minmax(375px, 1fr) minmax(375px, 1fr) + minmax(150px, 300px); + } +} diff --git a/plugins/main/public/components/endpoints-summary/endpoints-summary.tsx b/plugins/main/public/components/endpoints-summary/endpoints-summary.tsx index 494e2da137..7817da0711 100644 --- a/plugins/main/public/components/endpoints-summary/endpoints-summary.tsx +++ b/plugins/main/public/components/endpoints-summary/endpoints-summary.tsx @@ -12,25 +12,14 @@ */ import React, { Component } from 'react'; -import { - EuiPage, - EuiFlexGroup, - EuiFlexItem, - EuiStat, - EuiSpacer, - EuiToolTip, - EuiCard, - EuiLink, - EuiText, -} from '@elastic/eui'; +import { EuiPage, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { AgentsTable } from './table/agents-table'; -import { WzRequest } from '../../react-services/wz-request'; import WzReduxProvider from '../../redux/wz-redux-provider'; import { VisFactoryHandler } from '../../react-services/vis-factory-handler'; import { AppState } from '../../react-services/app-state'; import { FilterHandler } from '../../utils/filter-handler'; import { TabVisualizations } from '../../factories/tab-visualizations'; -import { WazuhConfig } from '../../react-services/wazuh-config.js'; +import { WazuhConfig } from '../../react-services/wazuh-config'; import { withReduxProvider, withGlobalBreadcrumb, @@ -38,22 +27,10 @@ import { withErrorBoundary, } from '../common/hocs'; import { compose } from 'redux'; -import { - UI_LOGGER_LEVELS, - UI_ORDER_AGENT_STATUS, -} from '../../../common/constants'; -import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; -import { getErrorOrchestrator } from '../../react-services/common-services'; -import { VisualizationBasic } from '../common/charts/visualizations/basic'; -import { - agentStatusColorByAgentStatus, - agentStatusLabelByAgentStatus, -} from '../../../common/services/wz_agent_status'; import { endpointSummary } from '../../utils/applications'; import { ShareAgent } from '../../factories/share-agent'; -import { getCore } from '../../kibana-services'; import './endpoints-summary.scss'; -import { RedirectAppLinks } from '../../../../../src/plugins/opensearch_dashboards_react/public'; +import { EndpointsSummaryDashboard } from './dashboard/endpoints-summary-dashboard'; export const EndpointsSummary = compose( withErrorBoundary, @@ -71,25 +48,25 @@ export const EndpointsSummary = compose( constructor() { super(); this.state = { - loadingSummary: true, - loadingLastRegisteredAgent: true, agentTableFilters: {}, - agentStatusSummary: [], - agentsActiveCoverage: undefined, + reload: 0, }; this.wazuhConfig = new WazuhConfig(); - this.agentStatus = UI_ORDER_AGENT_STATUS.map(agentStatus => ({ - status: agentStatus, - label: agentStatusLabelByAgentStatus(agentStatus), - color: agentStatusColorByAgentStatus(agentStatus), - })); this.shareAgent = new ShareAgent(); + this.filterAgentByStatus = this.filterAgentByStatus.bind(this); + this.filterAgentByOS = this.filterAgentByOS.bind(this); + this.filterAgentByGroup = this.filterAgentByGroup.bind(this); + this.filterByOutdatedAgent = this.filterByOutdatedAgent.bind(this); } + setReload = (newValue: number) => { + this.setState({ + reload: newValue, + }); + }; + async componentDidMount() { this._isMount = true; - this.getSummary(); - this.fetchLastRegisteredAgent(); if (this.wazuhConfig.getConfig()['wazuh.monitoring.enabled']) { const tabVisualizations = new TabVisualizations(); tabVisualizations.removeAll(); @@ -118,91 +95,42 @@ export const EndpointsSummary = compose( }, {}); }; - async getSummary() { - try { - this.setState({ loadingSummary: true }); - - const { - data: { - data: { - connection: agentStatusSummary, - configuration: agentConfiguration, - }, - }, - } = await WzRequest.apiReq('GET', '/agents/summary/status', {}); - - const agentsActiveCoverage = ( - (agentStatusSummary?.active / agentStatusSummary?.total) * - 100 - ).toFixed(2); - - this.setState({ - loadingSummary: false, - agentStatusSummary, - agentsActiveCoverage: isNaN(agentsActiveCoverage) - ? 0 - : agentsActiveCoverage, - }); - } catch (error) { + filterAgentByStatus(item: any) { + this._isMount && this.setState({ - loadingSummary: false, - agentStatusSummary: [], - agentsActiveCoverage: undefined, + agentTableFilters: { q: `id!=000;status=${item.status}` }, }); - const options = { - context: `EndpointsSummary.getSummary`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - store: true, - error: { - error: error, - message: error.message || error, - title: `Could not get agents summary`, - }, - }; - getErrorOrchestrator().handleError(options); - } } - async fetchLastRegisteredAgent() { - try { - this.setState({ loadingLastRegisteredAgent: true }); - const { - data: { - data: { - affected_items: [lastRegisteredAgent], - }, - }, - } = await WzRequest.apiReq('GET', '/agents', { - params: { limit: 1, sort: '-dateAdd', q: 'id!=000' }, - }); + filterAgentByOS(item: any) { + const query = + item.label === 'unknown' + ? 'id!=000;os.name=null' + : `id!=000;os.name~${item.label}`; + this._isMount && this.setState({ - loadingLastRegisteredAgent: false, - lastRegisteredAgent, + agentTableFilters: { q: query }, }); - } catch (error) { + } + + filterAgentByGroup(item: any) { + const query = + item.label === 'unknown' + ? 'id!=000;group=null' + : `id!=000;group=${item.label}`; + this._isMount && this.setState({ - loadingLastRegisteredAgent: false, + agentTableFilters: { q: query }, }); - const options = { - context: `EndpointsSummary.fetchLastRegisteredAgent`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - store: true, - error: { - error: error, - message: error.message || error, - title: `Could not get the last registered agent`, - }, - }; - getErrorOrchestrator().handleError(options); - } } - filterAgentByStatus(status) { + filterByOutdatedAgent(outdatedAgents: any) { + const ids: string = outdatedAgents + .map((agent: any) => `id=${agent.id}`) + .join(','); this._isMount && this.setState({ - agentTableFilters: { q: `id!=000;status=${status}` }, + agentTableFilters: { q: `id!=000;${ids}` }, }); } @@ -210,116 +138,19 @@ export const EndpointsSummary = compose( return ( - - - - - - ({ - label, - value: this.state.agentStatusSummary[status] || 0, - color, - onClick: () => this.filterAgentByStatus(status), - }), - )} - noDataTitle='No results' - noDataMessage='No results were found.' - /> - - - - - - - - {this.agentStatus.map(({ status, label, color }) => ( - - - this.filterAgentByStatus(status)} - style={{ cursor: 'pointer' }} - > - {this.state.agentStatusSummary[status]} - - - } - titleSize='s' - description={label} - titleColor={color} - className='white-space-nowrap' - /> - - ))} - - - - - - - - - - {this.state.lastRegisteredAgent?.name} - - - - ) : ( - - - ) - } - titleSize='s' - description='Last enrolled agent' - titleColor='primary' - /> - - - - - + - + diff --git a/plugins/main/public/components/endpoints-summary/hooks/agents.test.ts b/plugins/main/public/components/endpoints-summary/hooks/agents.test.ts new file mode 100644 index 0000000000..32afd959e7 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/hooks/agents.test.ts @@ -0,0 +1,38 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useGetTotalAgents } from './agents'; +import { getAgentsService } from '../services'; + +jest.mock('../services', () => ({ + getAgentsService: jest.fn(), +})); + +describe('useGetTotalAgents hook', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch initial data without any error', async () => { + (getAgentsService as jest.Mock).mockReturnValue({ + total_affected_items: 3, + }); + + const { result, waitForNextUpdate } = renderHook(() => useGetTotalAgents()); + + expect(result.current.isLoading).toBeTruthy(); + await waitForNextUpdate(); + expect(result.current.totalAgents).toEqual(3); + expect(result.current.isLoading).toBeFalsy(); + }); + + it('should handle error while fetching data', async () => { + const mockErrorMessage = 'Some error occurred'; + (getAgentsService as jest.Mock).mockRejectedValue(mockErrorMessage); + + const { result, waitForNextUpdate } = renderHook(() => useGetTotalAgents()); + + expect(result.current.isLoading).toBeTruthy(); + await waitForNextUpdate(); + expect(result.current.error).toBe(mockErrorMessage); + expect(result.current.isLoading).toBeFalsy(); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/hooks/agents.ts b/plugins/main/public/components/endpoints-summary/hooks/agents.ts index facb9df07b..3c383516fa 100644 --- a/plugins/main/public/components/endpoints-summary/hooks/agents.ts +++ b/plugins/main/public/components/endpoints-summary/hooks/agents.ts @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; -import { getTotalAgentsService } from '../services'; +import { getAgentsService } from '../services'; -export const useGetTotalAgents = () => { +export const useGetTotalAgents = (filters?: any) => { const [totalAgents, setTotalAgents] = useState(); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(); @@ -9,8 +9,11 @@ export const useGetTotalAgents = () => { const getTotalAgents = async () => { try { setIsLoading(true); - const totalAgents = await getTotalAgentsService(); - setTotalAgents(totalAgents); + const { total_affected_items } = await getAgentsService({ + filters, + limit: 1, + }); + setTotalAgents(total_affected_items); setError(undefined); } catch (error: any) { setError(error); diff --git a/plugins/main/public/components/endpoints-summary/hooks/groups.test.ts b/plugins/main/public/components/endpoints-summary/hooks/groups.test.ts new file mode 100644 index 0000000000..3dad828a43 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/hooks/groups.test.ts @@ -0,0 +1,44 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useGetGroups } from './groups'; +import { getGroupsService } from '../services'; + +jest.mock('../services', () => ({ + getGroupsService: jest.fn(), +})); + +describe('useGetGroups hook', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch initial data without any error', async () => { + (getGroupsService as jest.Mock).mockReturnValue({ + affected_items: [ + { name: 'group1' }, + { name: 'group2' }, + { name: 'group3' }, + ], + total_affected_items: 3, + }); + + const mockGroups = ['group1', 'group2', 'group3']; + const { result, waitForNextUpdate } = renderHook(() => useGetGroups()); + + expect(result.current.isLoading).toBeTruthy(); + await waitForNextUpdate(); + expect(result.current.groups).toEqual(mockGroups); + expect(result.current.isLoading).toBeFalsy(); + }); + + it('should handle error while fetching data', async () => { + const mockErrorMessage = 'Some error occurred'; + (getGroupsService as jest.Mock).mockRejectedValue(mockErrorMessage); + + const { result, waitForNextUpdate } = renderHook(() => useGetGroups()); + + expect(result.current.isLoading).toBeTruthy(); + await waitForNextUpdate(); + expect(result.current.error).toBe(mockErrorMessage); + expect(result.current.isLoading).toBeFalsy(); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/hooks/groups.ts b/plugins/main/public/components/endpoints-summary/hooks/groups.ts index 8c83e9dec3..4910e87ed8 100644 --- a/plugins/main/public/components/endpoints-summary/hooks/groups.ts +++ b/plugins/main/public/components/endpoints-summary/hooks/groups.ts @@ -9,7 +9,7 @@ export const useGetGroups = () => { const getGroups = async () => { try { setIsLoading(true); - const { affected_items } = await getGroupsService(); + const { affected_items } = await getGroupsService({}); const groups = affected_items.map(item => item.name); setGroups(groups); setError(undefined); diff --git a/plugins/main/public/components/endpoints-summary/hooks/index.ts b/plugins/main/public/components/endpoints-summary/hooks/index.ts index e1fdae97a1..063e5cc418 100644 --- a/plugins/main/public/components/endpoints-summary/hooks/index.ts +++ b/plugins/main/public/components/endpoints-summary/hooks/index.ts @@ -1 +1,2 @@ export { useGetTotalAgents } from './agents'; +export { useGetGroups } from './groups'; diff --git a/plugins/main/public/components/endpoints-summary/index.tsx b/plugins/main/public/components/endpoints-summary/index.tsx index 2d37a9c9d8..7b52d47946 100644 --- a/plugins/main/public/components/endpoints-summary/index.tsx +++ b/plugins/main/public/components/endpoints-summary/index.tsx @@ -25,7 +25,7 @@ export const MainEndpointsSummary = compose( withReduxProvider, withGlobalBreadcrumb([{ text: endpointSummary.breadcrumbLabel }]), )(() => { - const { isLoading, totalAgents, error } = useGetTotalAgents(); + const { isLoading, totalAgents, error } = useGetTotalAgents('id!=000'); if (error) { const options = { diff --git a/plugins/main/public/components/endpoints-summary/register-agent/components/server-address/server-address.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/server-address/server-address.tsx index 02b9cc8d1f..785851b767 100644 --- a/plugins/main/public/components/endpoints-summary/register-agent/components/server-address/server-address.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/components/server-address/server-address.tsx @@ -16,6 +16,7 @@ import { PLUGIN_VERSION_SHORT } from '../../../../../../common/constants'; import '../group-input/group-input.scss'; import { WzRequest } from '../../../../../react-services'; import { ErrorHandler } from '../../../../../react-services/error-management/error-handler/error-handler'; +import { WzButtonPermissions } from '../../../../common/permissions/button'; interface ServerAddressInputProps { formField: EnhancedFieldConfiguration; @@ -147,7 +148,9 @@ const ServerAddressInput = (props: ServerAddressInputProps) => {
- - await WzRequest.apiReq('PUT', `/agents/${agentId}/group/${group}`, {}); +export const addAgentToGroupService = async ({ + agentId, + groupId, +}: { + agentId: string; + groupId: string; +}) => + (await WzRequest.apiReq('PUT', `/agents/${agentId}/group/${groupId}`, { + wait_for_complete: true, + })) as IApiResponse; diff --git a/plugins/main/public/components/endpoints-summary/services/add-agents-to-group.tsx b/plugins/main/public/components/endpoints-summary/services/add-agents-to-group.tsx new file mode 100644 index 0000000000..5c563167f1 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/add-agents-to-group.tsx @@ -0,0 +1,9 @@ +import IApiResponse from '../../../react-services/interfaces/api-response.interface'; +import { paginatedAgentsGroupService } from './paginated-agents-group'; + +export const addAgentsToGroupService = async (parameters: { + agentIds: string[]; + groupId: string; + pageSize?: number; +}): Promise> => + await paginatedAgentsGroupService({ addOrRemove: 'add', ...parameters }); diff --git a/plugins/main/public/components/endpoints-summary/services/get-agents-by-group.test.ts b/plugins/main/public/components/endpoints-summary/services/get-agents-by-group.test.ts new file mode 100644 index 0000000000..4d025bbd67 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-agents-by-group.test.ts @@ -0,0 +1,92 @@ +import { getAgentsByGroup } from './get-agents-by-group'; +import { WzRequest } from '../../../react-services/wz-request'; +import { getColorPaletteByIndex } from './get-color-palette-by-index'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; + +jest.mock('../../../react-services/wz-request', () => ({ + WzRequest: { + apiReq: jest.fn(), + }, +})); + +jest.mock('./get-color-palette-by-index', () => ({ + getColorPaletteByIndex: jest.fn(), +})); + +jest.mock('../../../react-services/common-services', () => ({ + getErrorOrchestrator: jest.fn(), +})); + +describe('Get agents by group', () => { + it('should return grouped data', async () => { + const responseData = { + data: { + data: { + affected_items: [ + { + count: 2, + group: ['group1'], + }, + { + count: 1, + group: ['group2'], + }, + { + count: 4, + group: ['group1', 'group2'], + }, + ], + total_affected_items: 3, + total_failed_items: 0, + failed_items: [], + }, + message: 'All selected agents information was returned', + error: 0, + }, + }; + const expectedGroupedData = [ + { label: 'group1', value: 6, color: 'mockColor1' }, + { label: 'group2', value: 5, color: 'mockColor2' }, + ]; + + (WzRequest.apiReq as jest.Mock).mockResolvedValue(responseData); + + (getColorPaletteByIndex as jest.Mock).mockImplementation( + (index: number) => { + return `mockColor${index + 1}`; + }, + ); + + const groupedData = await getAgentsByGroup(); + + expect(groupedData).toEqual(expectedGroupedData); + }); + + it('should handle error', async () => { + const mockError = new Error('Mock error'); + + (WzRequest.apiReq as jest.Mock).mockRejectedValue(mockError); + + const mockHandleError = jest.fn(); + (getErrorOrchestrator as jest.Mock).mockReturnValue({ + handleError: mockHandleError, + }); + + const groupedData = await getAgentsByGroup(); + + expect(groupedData).toEqual([]); + expect(mockHandleError).toHaveBeenCalledWith({ + context: 'EndpointsSummary.getSummary', + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + error: { + error: mockError, + message: mockError.message || mockError, + title: 'Could not get agents summary', + }, + }); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/services/get-agents-by-group.ts b/plugins/main/public/components/endpoints-summary/services/get-agents-by-group.ts new file mode 100644 index 0000000000..7dda5067e9 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-agents-by-group.ts @@ -0,0 +1,75 @@ +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { WzRequest } from '../../../react-services/wz-request'; +import { getColorPaletteByIndex } from './get-color-palette-by-index'; + +interface AffectedItem { + count: number; + group?: string[]; +} + +interface AgentCountGroup { + label: string; + value: number; + color: string; +} + +export const getAgentsByGroup = async () => { + try { + const { + data: { + data: { affected_items }, + }, + }: any = await WzRequest.apiReq( + 'GET', + '/agents/stats/distinct?fields=group', + { + params: { q: 'id!=000' }, + }, + ); + const groupedData = getCountByGroup(affected_items); + return groupedData.slice(0, 5); + } catch (error) { + const options = { + context: `EndpointsSummary.getSummary`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + error: { + error: error, + message: error.message || error, + title: `Could not get agents summary`, + }, + }; + getErrorOrchestrator().handleError(options); + return []; + } +}; + +function getCountByGroup(data: AffectedItem[]): AgentCountGroup[] { + const countMap: Map = new Map(); + + data.forEach(item => { + if (item.group) { + item.group.forEach(group => { + if (countMap.has(group)) { + countMap.set(group, countMap.get(group)! + item.count); + } else { + countMap.set(group, item.count); + } + }); + } + }); + + const countArray = Array.from(countMap.entries()).map( + ([label, value], index: number) => { + return { + label, + value, + color: getColorPaletteByIndex(index), + }; + }, + ); + return countArray.sort((a, b) => b.value - a.value); +} diff --git a/plugins/main/public/components/endpoints-summary/services/get-agents-by-os.test.ts b/plugins/main/public/components/endpoints-summary/services/get-agents-by-os.test.ts new file mode 100644 index 0000000000..abca1d309a --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-agents-by-os.test.ts @@ -0,0 +1,79 @@ +import { getAgentsByOs } from './get-agents-by-os'; +import { WzRequest } from '../../../react-services/wz-request'; +import { getColorPaletteByIndex } from './get-color-palette-by-index'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; + +jest.mock('../../../react-services/wz-request', () => ({ + WzRequest: { + apiReq: jest.fn(), + }, +})); + +jest.mock('./get-color-palette-by-index', () => ({ + getColorPaletteByIndex: jest.fn(), +})); + +jest.mock('../../../react-services/common-services', () => ({ + getErrorOrchestrator: jest.fn(), +})); + +describe('getAgentsByOs', () => { + it('should return grouped data', async () => { + const responseData = { + data: { + data: { + affected_items: [ + { os: { platform: 'Windows' }, count: 3 }, + { os: { platform: 'Linux' }, count: 2 }, + { os: { platform: 'Mac' }, count: 1 }, + ], + }, + }, + }; + const expectedGroupedData = [ + { label: 'Windows', value: 3, color: 'mockColor1' }, + { label: 'Linux', value: 2, color: 'mockColor2' }, + { label: 'Mac', value: 1, color: 'mockColor3' }, + ]; + + (WzRequest.apiReq as jest.Mock).mockResolvedValue(responseData); + + (getColorPaletteByIndex as jest.Mock).mockImplementation( + (index: number) => { + return `mockColor${index + 1}`; + }, + ); + + const groupedData = await getAgentsByOs(); + + expect(groupedData).toEqual(expectedGroupedData); + }); + + it('should handle error', async () => { + const mockError = new Error('Mock error'); + + (WzRequest.apiReq as jest.Mock).mockRejectedValue(mockError); + + const mockHandleError = jest.fn(); + (getErrorOrchestrator as jest.Mock).mockReturnValue({ + handleError: mockHandleError, + }); + + const groupedData = await getAgentsByOs(); + + expect(groupedData).toEqual([]); + expect(mockHandleError).toHaveBeenCalledWith({ + context: 'EndpointsSummary.getSummary', + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + error: { + error: mockError, + message: mockError.message || mockError, + title: 'Could not get agents by OS', + }, + }); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/services/get-agents-by-os.ts b/plugins/main/public/components/endpoints-summary/services/get-agents-by-os.ts new file mode 100644 index 0000000000..de43ea8bec --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-agents-by-os.ts @@ -0,0 +1,46 @@ +import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { WzRequest } from '../../../react-services/wz-request'; +import { getColorPaletteByIndex } from './get-color-palette-by-index'; + +export const getAgentsByOs = async () => { + const DEFAULT_COUNT = 1; + try { + const { + data: { + data: { affected_items }, + }, + }: any = await WzRequest.apiReq( + 'GET', + '/agents/stats/distinct?fields=os.platform', + { + params: { q: 'id!=000' }, + }, + ); + const groupedData: any[] = []; + affected_items?.forEach((item: any, index: number) => { + const itemOsName = item?.os?.platform ?? 'unknown'; + groupedData.push({ + label: itemOsName, + value: item.count ?? DEFAULT_COUNT, + color: getColorPaletteByIndex(index), + }); + }); + return groupedData.sort((a, b) => b.value - a.value).slice(0, 5); + } catch (error) { + const options = { + context: `EndpointsSummary.getSummary`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + error: { + error: error, + message: error.message || error, + title: `Could not get agents by OS`, + }, + }; + getErrorOrchestrator().handleError(options); + return []; + } +}; diff --git a/plugins/main/public/components/endpoints-summary/services/get-agents.test.tsx b/plugins/main/public/components/endpoints-summary/services/get-agents.test.tsx new file mode 100644 index 0000000000..2293b3373b --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-agents.test.tsx @@ -0,0 +1,85 @@ +import { getAgentsService } from './get-agents'; +import { WzRequest } from '../../../react-services/wz-request'; + +jest.mock('../../../react-services/wz-request', () => ({ + WzRequest: { + apiReq: jest.fn(), + }, +})); + +describe('getAgentsService', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should paginate agents and handle API responses correctly', async () => { + (WzRequest.apiReq as jest.Mock).mockImplementation( + async (method, endpoint, options) => { + if (options.params.offset === 0) { + return { + data: { + data: { + affected_items: [ + { id: '001', name: 'agent1' }, + { id: '002', name: 'agent2' }, + ], + total_affected_items: 3, + failed_items: [], + total_failed_items: 0, + }, + error: 0, + message: 'Success', + }, + }; + } else { + return { + data: { + data: { + affected_items: [{ id: '003', name: 'agent3' }], + total_affected_items: 3, + failed_items: [], + total_failed_items: 0, + }, + error: 0, + message: 'Success', + }, + }; + } + }, + ); + + const params = { + filters: {}, + pageSize: 2, + }; + + const result = await getAgentsService(params); + + expect(WzRequest.apiReq).toHaveBeenCalledWith('GET', '/agents', { + params: { + q: {}, + limit: 2, + offset: 0, + wait_for_complete: true, + }, + }); + + expect(WzRequest.apiReq).toHaveBeenCalledWith('GET', '/agents', { + params: { + q: {}, + limit: 2, + offset: 2, + wait_for_complete: true, + }, + }); + + expect(result).toEqual({ + affected_items: [ + { id: '001', name: 'agent1' }, + { id: '002', name: 'agent2' }, + { id: '003', name: 'agent3' }, + ], + total_affected_items: 3, + }); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/services/get-agents.tsx b/plugins/main/public/components/endpoints-summary/services/get-agents.tsx new file mode 100644 index 0000000000..ebe67f445e --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-agents.tsx @@ -0,0 +1,54 @@ +import IApiResponse from '../../../react-services/interfaces/api-response.interface'; +import { WzRequest } from '../../../react-services/wz-request'; +import { Agent } from '../types'; + +export const getAgentsService = async ({ + filters, + limit, + offset, + pageSize = 1000, +}: { + filters: any; + limit?: number; + offset?: number; + pageSize?: number; +}) => { + let queryOffset = offset ?? 0; + let queryLimit = limit && limit <= pageSize ? limit : pageSize; + let allAffectedItems: Agent[] = []; + let totalAffectedItems; + + do { + const { + data: { + data: { affected_items, total_affected_items }, + }, + } = (await WzRequest.apiReq('GET', '/agents', { + params: { + limit: queryLimit, + offset: queryOffset, + q: filters, + wait_for_complete: true, + }, + })) as IApiResponse; + + if (totalAffectedItems === undefined) { + totalAffectedItems = total_affected_items; + } + + allAffectedItems = allAffectedItems.concat(affected_items); + + queryOffset += queryLimit; + + const restItems = limit ? limit - allAffectedItems.length : pageSize; + queryLimit = restItems > pageSize ? pageSize : restItems; + } while ( + queryOffset < totalAffectedItems && + (!limit || allAffectedItems.length < limit) + ); + + return { + affected_items: allAffectedItems, + total_affected_items: totalAffectedItems, + }; +}; diff --git a/plugins/main/public/components/endpoints-summary/services/get-color-palette-by-index.tsx b/plugins/main/public/components/endpoints-summary/services/get-color-palette-by-index.tsx new file mode 100644 index 0000000000..9c5f651cfe --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-color-palette-by-index.tsx @@ -0,0 +1,12 @@ +import { euiPaletteColorBlind } from '@elastic/eui'; + +export function getColorPaletteByIndex(index: number) { + const colorPalette = euiPaletteColorBlind({ + rotations: 9, + direction: 'both', + order: 'middle-out', + }); + const validIndex = + index < colorPalette.length ? index : index - colorPalette.length; + return colorPalette[validIndex]; +} diff --git a/plugins/main/public/components/endpoints-summary/services/get-groups.test.tsx b/plugins/main/public/components/endpoints-summary/services/get-groups.test.tsx new file mode 100644 index 0000000000..f9f78d2a6f --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-groups.test.tsx @@ -0,0 +1,78 @@ +import { getGroupsService } from './get-groups'; +import { WzRequest } from '../../../react-services/wz-request'; + +jest.mock('../../../react-services/wz-request', () => ({ + WzRequest: { + apiReq: jest.fn(), + }, +})); + +describe('getGroupsService', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should paginate groups and handle API responses correctly', async () => { + (WzRequest.apiReq as jest.Mock).mockImplementation( + async (method, endpoint, options) => { + if (options.params.offset === 0) { + return { + data: { + data: { + affected_items: ['group1', 'group2'], + total_affected_items: 3, + failed_items: [], + total_failed_items: 0, + }, + error: 0, + message: 'Success', + }, + }; + } else { + return { + data: { + data: { + affected_items: ['group3'], + total_affected_items: 3, + failed_items: [], + total_failed_items: 0, + }, + error: 0, + message: 'Success', + }, + }; + } + }, + ); + + const params = { + filters: {}, + pageSize: 2, + }; + + const result = await getGroupsService(params); + + expect(WzRequest.apiReq).toHaveBeenCalledWith('GET', '/groups', { + params: { + q: {}, + limit: 2, + offset: 0, + wait_for_complete: true, + }, + }); + + expect(WzRequest.apiReq).toHaveBeenCalledWith('GET', '/groups', { + params: { + q: {}, + limit: 2, + offset: 2, + wait_for_complete: true, + }, + }); + + expect(result).toEqual({ + affected_items: ['group1', 'group2', 'group3'], + total_affected_items: 3, + }); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/services/get-groups.tsx b/plugins/main/public/components/endpoints-summary/services/get-groups.tsx index 1b463c163d..a565e1f3cb 100644 --- a/plugins/main/public/components/endpoints-summary/services/get-groups.tsx +++ b/plugins/main/public/components/endpoints-summary/services/get-groups.tsx @@ -1,8 +1,54 @@ +import IApiResponse from '../../../react-services/interfaces/api-response.interface'; import { WzRequest } from '../../../react-services/wz-request'; +import { Group } from '../types'; -export const getGroupsService = async () => { - const { - data: { data }, - } = await WzRequest.apiReq('GET', '/groups', {}); - return data; +export const getGroupsService = async ({ + filters, + limit, + offset, + pageSize = 1000, +}: { + filters: any; + limit?: number; + offset?: number; + pageSize?: number; +}) => { + let queryOffset = offset ?? 0; + let queryLimit = limit && limit <= pageSize ? limit : pageSize; + let allAffectedItems: Group[] = []; + let totalAffectedItems; + + do { + const { + data: { + data: { affected_items, total_affected_items }, + }, + } = (await WzRequest.apiReq('GET', '/groups', { + params: { + limit: queryLimit, + offset: queryOffset, + q: filters, + wait_for_complete: true, + }, + })) as IApiResponse; + + if (totalAffectedItems === undefined) { + totalAffectedItems = total_affected_items; + } + + allAffectedItems = allAffectedItems.concat(affected_items); + + queryOffset += queryLimit; + + const restItems = limit ? limit - allAffectedItems.length : pageSize; + queryLimit = restItems > pageSize ? pageSize : restItems; + } while ( + queryOffset < totalAffectedItems && + (!limit || allAffectedItems.length < limit) + ); + + return { + affected_items: allAffectedItems, + total_affected_items: totalAffectedItems, + }; }; diff --git a/plugins/main/public/components/endpoints-summary/services/get-outdated-agents.tsx b/plugins/main/public/components/endpoints-summary/services/get-outdated-agents.tsx new file mode 100644 index 0000000000..cd35c2d442 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-outdated-agents.tsx @@ -0,0 +1,10 @@ +import { WzRequest } from '../../../react-services/wz-request'; + +export const getOutdatedAgents = async () => { + const { + data: { + data: { affected_items }, + }, + } = await WzRequest.apiReq('GET', '/agents/outdated', {}); + return affected_items; +}; diff --git a/plugins/main/public/components/endpoints-summary/services/get-summary-agents-status.ts b/plugins/main/public/components/endpoints-summary/services/get-summary-agents-status.ts new file mode 100644 index 0000000000..b55863c462 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/get-summary-agents-status.ts @@ -0,0 +1,47 @@ +import { + UI_LOGGER_LEVELS, + UI_ORDER_AGENT_STATUS, +} from '../../../../common/constants'; +import { + agentStatusLabelByAgentStatus, + agentStatusColorByAgentStatus, +} from '../../../../common/services/wz_agent_status'; +import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; +import { WzRequest } from '../../../react-services/wz-request'; + +export const getSummaryAgentsStatus = async () => { + try { + const AGENT_STATUS = UI_ORDER_AGENT_STATUS.map(agentStatus => ({ + status: agentStatus, + label: agentStatusLabelByAgentStatus(agentStatus), + color: agentStatusColorByAgentStatus(agentStatus), + })); + const { + data: { + data: { connection: agentStatusSummary }, + }, + }: any = await WzRequest.apiReq('GET', '/agents/summary/status', {}); + + return AGENT_STATUS.map(({ label, status, color }) => ({ + status, + label: label, + value: agentStatusSummary[status] || 0, + color: color, + })); + } catch (error) { + const options = { + context: `EndpointsSummary.getSummary`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + store: true, + error: { + error: error, + message: error.message || error, + title: `Could not get agents summary`, + }, + }; + getErrorOrchestrator().handleError(options); + return []; + } +}; diff --git a/plugins/main/public/components/endpoints-summary/services/get-total-agents.tsx b/plugins/main/public/components/endpoints-summary/services/get-total-agents.tsx deleted file mode 100644 index b75c59725e..0000000000 --- a/plugins/main/public/components/endpoints-summary/services/get-total-agents.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { WzRequest } from '../../../react-services/wz-request'; - -export const getTotalAgentsService = async () => { - const { - data: { - data: { total_affected_items }, - }, - } = await WzRequest.apiReq('GET', '/agents', { - params: { limit: 1, q: 'id!=000' }, - }); - return total_affected_items; -}; diff --git a/plugins/main/public/components/endpoints-summary/services/index.tsx b/plugins/main/public/components/endpoints-summary/services/index.tsx index 6615483443..a8d7711ff8 100644 --- a/plugins/main/public/components/endpoints-summary/services/index.tsx +++ b/plugins/main/public/components/endpoints-summary/services/index.tsx @@ -1,5 +1,6 @@ -export { getTotalAgentsService } from './get-total-agents'; -export { removeAgentFromGroupService } from './remove-agent-from-group'; +export { getAgentsService } from './get-agents'; export { removeAgentFromGroupsService } from './remove-agent-from-groups'; +export { removeAgentsFromGroupService } from './remove-agents-from-group'; export { addAgentToGroupService } from './add-agent-to-group'; +export { addAgentsToGroupService } from './add-agents-to-group'; export { getGroupsService } from './get-groups'; diff --git a/plugins/main/public/components/endpoints-summary/services/paginated-agents-group.test.tsx b/plugins/main/public/components/endpoints-summary/services/paginated-agents-group.test.tsx new file mode 100644 index 0000000000..e6f506dfe0 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/paginated-agents-group.test.tsx @@ -0,0 +1,205 @@ +import { paginatedAgentsGroupService } from './paginated-agents-group'; +import { WzRequest } from '../../../react-services/wz-request'; + +jest.mock('../../../react-services/wz-request', () => ({ + WzRequest: { + apiReq: jest.fn(), + }, +})); + +describe('paginatedAgentsGroupService', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should paginate agents and handle API responses correctly', async () => { + (WzRequest.apiReq as jest.Mock).mockImplementation( + async (method, endpoint, options) => { + if (options.params.agents_list === 'agent1,agent2') { + return { + data: { + data: { + affected_items: ['agent1', 'agent2'], + total_affected_items: 2, + failed_items: [], + total_failed_items: 0, + }, + error: 0, + message: 'Success', + }, + }; + } else { + return { + data: { + data: { + affected_items: ['agent3'], + total_affected_items: 1, + failed_items: [], + total_failed_items: 0, + }, + error: 0, + message: 'Success', + }, + }; + } + }, + ); + + const params = { + addOrRemove: 'add' as any, + agentIds: ['agent1', 'agent2', 'agent3'], + groupId: 'group1', + pageSize: 2, + }; + + const result = await paginatedAgentsGroupService(params); + + expect(WzRequest.apiReq).toHaveBeenCalledWith( + 'PUT', + '/agents/group', + { + params: { + group_id: 'group1', + agents_list: 'agent1,agent2', + wait_for_complete: true, + }, + }, + { returnOriginalResponse: true }, + ); + + expect(WzRequest.apiReq).toHaveBeenCalledWith( + 'PUT', + '/agents/group', + { + params: { + group_id: 'group1', + agents_list: 'agent3', + wait_for_complete: true, + }, + }, + { returnOriginalResponse: true }, + ); + + expect(result).toEqual({ + data: { + data: { + affected_items: ['agent1', 'agent2', 'agent3'], + total_affected_items: 3, + failed_items: [], + total_failed_items: 0, + }, + error: 0, + message: 'Success', + }, + }); + }); + + it('should paginate agents and handle API responses with failed items', async () => { + (WzRequest.apiReq as jest.Mock).mockImplementation( + async (method, endpoint, options) => { + if (options.params.agents_list === 'agent1,agent2') { + return { + data: { + data: { + affected_items: ['agent1'], + total_affected_items: 1, + failed_items: [ + { + error: { + code: '001', + message: 'agent error', + remediation: 'example remediation', + }, + id: ['agent2'], + }, + ], + total_failed_items: 1, + }, + error: 1, + message: 'agent2 error', + }, + }; + } else { + return { + data: { + data: { + affected_items: [], + total_affected_items: 0, + failed_items: [ + { + error: { + code: '001', + message: 'agent error', + remediation: 'example remediation', + }, + id: ['agent3'], + }, + ], + total_failed_items: 1, + }, + error: 1, + message: 'agent3 error', + }, + }; + } + }, + ); + + const params = { + addOrRemove: 'add' as any, + agentIds: ['agent1', 'agent2', 'agent3'], + groupId: 'group1', + pageSize: 2, + }; + + const result = await paginatedAgentsGroupService(params); + + expect(WzRequest.apiReq).toHaveBeenCalledWith( + 'PUT', + '/agents/group', + { + params: { + group_id: 'group1', + agents_list: 'agent1,agent2', + wait_for_complete: true, + }, + }, + { returnOriginalResponse: true }, + ); + + expect(WzRequest.apiReq).toHaveBeenCalledWith( + 'PUT', + '/agents/group', + { + params: { + group_id: 'group1', + agents_list: 'agent3', + wait_for_complete: true, + }, + }, + { returnOriginalResponse: true }, + ); + + expect(result).toEqual({ + data: { + data: { + affected_items: ['agent1'], + total_affected_items: 1, + failed_items: [ + { + error: { + code: '001', + message: 'agent error', + remediation: 'example remediation', + }, + id: ['agent2', 'agent3'], + }, + ], + total_failed_items: 2, + }, + error: 2, + message: 'agent2 error, agent3 error', + }, + }); + }); +}); diff --git a/plugins/main/public/components/endpoints-summary/services/paginated-agents-group.tsx b/plugins/main/public/components/endpoints-summary/services/paginated-agents-group.tsx new file mode 100644 index 0000000000..512098817c --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/paginated-agents-group.tsx @@ -0,0 +1,109 @@ +import IApiResponse from '../../../react-services/interfaces/api-response.interface'; +import { WzRequest } from '../../../react-services/wz-request'; + +export type ErrorAgent = { + error: { + code?: number; + message: string; + remediation?: string; + }; + id: string[]; +}; + +export const paginatedAgentsGroupService = async ({ + addOrRemove, + agentIds, + groupId, + pageSize = 1000, +}: { + addOrRemove: 'add' | 'remove'; + agentIds: string[]; + groupId: string; + pageSize?: number; +}): Promise> => { + let offset = 0; + let requestAgentIds: string[] = []; + let allAffectedItems: string[] = []; + let allFailedItems: ErrorAgent[] = []; + let totalAffectedItems = 0; + let totalFailedItems = 0; + let error = 0; + let message = ''; + + do { + requestAgentIds = agentIds.slice(offset, offset + pageSize); + + const { + data: { + data: { + affected_items: responseAffectedItems, + total_affected_items: responseTotalAffectedItems, + failed_items: responseFailedItems, + total_failed_items: responseTotalFailedItems, + }, + error: responseError, + message: responseMessage, + }, + } = (await WzRequest.apiReq( + addOrRemove === 'add' ? 'PUT' : 'DELETE', + `/agents/group`, + { + params: { + group_id: groupId, + agents_list: requestAgentIds.join(','), + wait_for_complete: true, + }, + }, + { returnOriginalResponse: true }, + )) as IApiResponse; + + error += responseError; + message = + offset === 0 + ? responseMessage + : message.includes(responseMessage) + ? message + : message + ', ' + responseMessage; + totalAffectedItems += responseTotalAffectedItems; + totalFailedItems += responseTotalFailedItems; + allAffectedItems = [...allAffectedItems, ...responseAffectedItems]; + + const notExistFailedItems = responseFailedItems.filter( + responseFailedItem => + !allFailedItems.find( + failedItem => failedItem.error.code === responseFailedItem.error.code, + ), + ); + + const mergeFailedItems = allFailedItems.map(failedItem => { + const responseFailedItemWithSameError = responseFailedItems.find( + responseFailedItem => + responseFailedItem.error.code === failedItem.error.code, + ); + + return { + ...failedItem, + id: responseFailedItemWithSameError + ? [...failedItem.id, ...responseFailedItemWithSameError.id] + : failedItem.id, + }; + }); + + allFailedItems = [...mergeFailedItems, ...notExistFailedItems]; + + offset += pageSize; + } while (offset < agentIds.length); + + return { + data: { + data: { + affected_items: allAffectedItems, + total_affected_items: totalAffectedItems, + failed_items: allFailedItems, + total_failed_items: totalFailedItems, + }, + error, + message, + }, + }; +}; diff --git a/plugins/main/public/components/endpoints-summary/services/remove-agent-from-group.tsx b/plugins/main/public/components/endpoints-summary/services/remove-agent-from-group.tsx deleted file mode 100644 index 633d49fe41..0000000000 --- a/plugins/main/public/components/endpoints-summary/services/remove-agent-from-group.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { WzRequest } from '../../../react-services/wz-request'; - -export const removeAgentFromGroupService = async ( - agentId: string, - group: string, -) => await WzRequest.apiReq('DELETE', `/agents/${agentId}/group/${group}`, {}); diff --git a/plugins/main/public/components/endpoints-summary/services/remove-agent-from-groups.tsx b/plugins/main/public/components/endpoints-summary/services/remove-agent-from-groups.tsx index 2784d026aa..2825670e82 100644 --- a/plugins/main/public/components/endpoints-summary/services/remove-agent-from-groups.tsx +++ b/plugins/main/public/components/endpoints-summary/services/remove-agent-from-groups.tsx @@ -1,11 +1,15 @@ import { WzRequest } from '../../../react-services/wz-request'; -export const removeAgentFromGroupsService = async ( - agentId: string, - groups: string[], -) => +export const removeAgentFromGroupsService = async ({ + agentId, + groupIds, +}: { + agentId: string; + groupIds: string[]; +}) => await WzRequest.apiReq('DELETE', `/agents/${agentId}/group`, { params: { - groups_list: groups.join(','), + groups_list: groupIds.join(','), + wait_for_complete: true, }, }); diff --git a/plugins/main/public/components/endpoints-summary/services/remove-agents-from-group.tsx b/plugins/main/public/components/endpoints-summary/services/remove-agents-from-group.tsx new file mode 100644 index 0000000000..a2000e9ba4 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/services/remove-agents-from-group.tsx @@ -0,0 +1,9 @@ +import IApiResponse from '../../../react-services/interfaces/api-response.interface'; +import { paginatedAgentsGroupService } from './paginated-agents-group'; + +export const removeAgentsFromGroupService = async (parameters: { + agentIds: string[]; + groupId: string; + pageSize?: number; +}): Promise> => + await paginatedAgentsGroupService({ addOrRemove: 'remove', ...parameters }); diff --git a/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap b/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap index 6f16d19e0b..5a0be00f9f 100644 --- a/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap +++ b/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap @@ -12,229 +12,85 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = ` class="euiFlexItem" >
-
-

- Agents - - (0) - -

-
-
- -
-
- + Agents + + (0) + + +
+
- - - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
+ + + + Deploy new agent + + +
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - -
-
- - - - - +
-
- - +
- -
+ + + + + + +
+
+
+
+
- -
- - + + + + + + + + + +
+
+
+
+
- - - Actions - - -
+ +
+ +
+
- - No items found - +
+
+ +
+
+
-
-
-
-
-
-
-
-`; - -exports[`AgentsTable component Renders correctly to match the snapshot with custom columns 1`] = ` -
-
-
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + Actions + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+`; + +exports[`AgentsTable component Renders correctly to match the snapshot with custom columns 1`] = ` +
+
+
-
-

- Agents - - (0) - -

-
-
- -
-
- + Agents + + (0) + + +
+
- - - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
+ Deploy new agent + + +
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - -
-
- - - +
-
- - +
- -
+ + + + + + +
+
+
+
+
- -
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
- - - Actions - - -
+ +
+ +
+
- - No items found - +
+
+ +
+
+
-
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + Actions + + +
+
+ + No items found + +
+
+
+
@@ -1038,250 +1275,86 @@ exports[`AgentsTable component Renders correctly to match the snapshot with no p class="euiFlexItem" >
-
-

- Agents - - (0) - -

-
-
- -
-
- + Agents + + (0) + + +
+
- - - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
+ Deploy new agent + + +
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - + +
-
- - - +
-
+
- -
+ + + + + + +
+
+
+
+
- -
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
- -
- - - + + + + + + + + + + + + + + + + + + + - - - - - - + + + - - No items found - - - - - -
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + - Status + + Actions + - - - - - - Actions - - -
-
+
+
+ + No items found + +
+ +
+
+
diff --git a/plugins/main/public/components/endpoints-summary/table/actions/__snapshots__/edit-groups-modal.test.tsx.snap b/plugins/main/public/components/endpoints-summary/table/actions/__snapshots__/edit-groups-modal.test.tsx.snap new file mode 100644 index 0000000000..29c18c734e --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/table/actions/__snapshots__/edit-groups-modal.test.tsx.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EditAgentGroupsModal component should return the component 1`] = ` +