From 7fbb0f6aaff43869a38a6f4802a4590eb16e208b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20D=C3=ADaz?= Date: Sat, 14 Oct 2023 10:43:42 +0200 Subject: [PATCH 001/136] Bump to 4.9.0-00 (#6004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump to 4.9.0-00 * bump: mock server to 4.9.0 * fix(test): update snapshosts * changelog: add change to 4.9.0 --------- Co-authored-by: Antonio David GutiƩrrez --- CHANGELOG.md | 8 +- docker/imposter/api-info/api_info.json | 2 +- plugins/main/opensearch_dashboards.json | 4 +- plugins/main/package.json | 6 +- .../__snapshots__/agent-status.test.tsx.snap | 6 +- .../__snapshots__/inventory.test.tsx.snap | 84 +++++++++---------- .../table-with-search-bar.test.tsx.snap | 10 +-- .../__snapshots__/agent-table.test.tsx.snap | 32 +++---- .../server-address.test.tsx.snap | 2 +- .../SubscriptionTab.test.tsx.snap | 8 +- .../__snapshots__/api-auth-tab.test.tsx.snap | 8 +- .../__snapshots__/general-tab.test.tsx.snap | 8 +- 12 files changed, 92 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0564187b23..c7a03075ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ All notable changes to the Wazuh app project will be documented in this file. -## Wazuh v4.8.0 - OpenSearch Dashboards 2.9.0 - Revision 00 +## Wazuh v4.9.0 - OpenSearch Dashboards 2.10.0 - Revision 00 + +### Added + +- Support for Wazuh 4.9.0 + +## Wazuh v4.8.0 - OpenSearch Dashboards 2.10.0 - Revision 00 ### Added diff --git a/docker/imposter/api-info/api_info.json b/docker/imposter/api-info/api_info.json index 760da093a4..0d122b0ce2 100644 --- a/docker/imposter/api-info/api_info.json +++ b/docker/imposter/api-info/api_info.json @@ -1,7 +1,7 @@ { "data": { "title": "Wazuh API REST", - "api_version": "4.8.0", + "api_version": "4.9.0", "revision": 1, "license_name": "GPL 2.0", "license_url": "https://github.com/wazuh/wazuh/blob/4.5/LICENSE", diff --git a/plugins/main/opensearch_dashboards.json b/plugins/main/opensearch_dashboards.json index 45fdb57a36..e6483d260b 100644 --- a/plugins/main/opensearch_dashboards.json +++ b/plugins/main/opensearch_dashboards.json @@ -1,6 +1,6 @@ { "id": "wazuh", - "version": "4.8.0-00", + "version": "4.9.0-00", "opensearchDashboardsVersion": "opensearchDashboards", "configPath": [ "wazuh" @@ -26,4 +26,4 @@ ], "server": true, "ui": true -} \ No newline at end of file +} diff --git a/plugins/main/package.json b/plugins/main/package.json index 3c3826d3ba..2d053c3367 100644 --- a/plugins/main/package.json +++ b/plugins/main/package.json @@ -1,9 +1,9 @@ { "name": "wazuh", - "version": "4.8.0", + "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.9.0" + "version": "2.10.0" }, "description": "Wazuh dashboard", "keywords": [ @@ -85,4 +85,4 @@ "redux-mock-store": "^1.5.4", "swagger-client": "^3.19.11" } -} \ No newline at end of file +} 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 e9894d11db..6253e66ffd 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 @@ -118,7 +118,7 @@ exports[`AgentStatus component Renders status indicator with the its color and t xmlns="http://www.w3.org/2000/svg" > @@ -185,7 +185,7 @@ exports[`AgentStatus component Renders status indicator with the its color and t xmlns="http://www.w3.org/2000/svg" > @@ -252,7 +252,7 @@ exports[`AgentStatus component Renders status indicator with the its color and t xmlns="http://www.w3.org/2000/svg" > diff --git a/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap b/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap index 539eeb3f55..f515d70f35 100644 --- a/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap +++ b/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap @@ -147,7 +147,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -352,7 +352,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -531,7 +531,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -865,7 +865,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1070,7 +1070,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1252,7 +1252,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1457,7 +1457,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1641,7 +1641,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -1847,7 +1847,7 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -4279,7 +4279,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -4484,7 +4484,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -4663,7 +4663,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -5021,7 +5021,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -5226,7 +5226,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -5405,7 +5405,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -5610,7 +5610,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -5696,7 +5696,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -5901,7 +5901,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -6061,7 +6061,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > @@ -6267,7 +6267,7 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = ` xmlns="http://www.w3.org/2000/svg" > diff --git a/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap b/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap index 8fd2d4bcea..78c2233732 100644 --- a/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap +++ b/plugins/main/public/components/common/tables/__snapshots__/table-with-search-bar.test.tsx.snap @@ -109,7 +109,7 @@ exports[`Table With Search Bar component renders correctly to match the snapshot
@@ -178,7 +178,7 @@ exports[`Table With Search Bar component renders correctly to match the snapshot
@@ -258,7 +258,7 @@ exports[`Table With Search Bar component renders correctly to match the snapshot
@@ -368,7 +368,7 @@ exports[`Table With Search Bar component renders correctly to match the snapshot
@@ -434,7 +434,7 @@ exports[`Table With Search Bar component renders correctly to match the snapshot
diff --git a/plugins/main/public/controllers/agent/components/__snapshots__/agent-table.test.tsx.snap b/plugins/main/public/controllers/agent/components/__snapshots__/agent-table.test.tsx.snap index 828132dbed..c13fb6e2cd 100644 --- a/plugins/main/public/controllers/agent/components/__snapshots__/agent-table.test.tsx.snap +++ b/plugins/main/public/controllers/agent/components/__snapshots__/agent-table.test.tsx.snap @@ -611,7 +611,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with cust xmlns="http://www.w3.org/2000/svg" > @@ -644,7 +644,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with cust xmlns="http://www.w3.org/2000/svg" > @@ -811,7 +811,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with cust xmlns="http://www.w3.org/2000/svg" > @@ -928,7 +928,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with cust xmlns="http://www.w3.org/2000/svg" > @@ -1155,7 +1155,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with no p xmlns="http://www.w3.org/2000/svg" > @@ -1188,7 +1188,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with no p xmlns="http://www.w3.org/2000/svg" > @@ -1355,7 +1355,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with no p xmlns="http://www.w3.org/2000/svg" > @@ -1472,7 +1472,7 @@ exports[`AgentsTable component Renders correctly to match the snapshot with no p xmlns="http://www.w3.org/2000/svg" > diff --git a/plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap b/plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap index d41e54d637..5326643f51 100644 --- a/plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap +++ b/plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap @@ -70,7 +70,7 @@ exports[`Server Address Combobox should match snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > diff --git a/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap b/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap index 7779a6ec74..e0d47aa5f6 100644 --- a/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap +++ b/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap @@ -36,11 +36,11 @@ exports[`SubscriptionTab component renders correctly to match the snapshot 1`] = help={ Array [ Object { - "href": "https://documentation.wazuh.com/4.8/cloud-security/office365/index.html", + "href": "https://documentation.wazuh.com/4.9/cloud-security/office365/index.html", "text": "Using Wazuh to monitor Office 365", }, Object { - "href": "https://documentation.wazuh.com/4.8/user-manual/reference/ossec-conf/office365-module.html", + "href": "https://documentation.wazuh.com/4.9/user-manual/reference/ossec-conf/office365-module.html", "text": "Configuration options for the module", }, ] @@ -87,11 +87,11 @@ exports[`SubscriptionTab component renders correctly to match the snapshot 1`] = links={ Array [ Object { - "href": "https://documentation.wazuh.com/4.8/cloud-security/office365/index.html", + "href": "https://documentation.wazuh.com/4.9/cloud-security/office365/index.html", "text": "Using Wazuh to monitor Office 365", }, Object { - "href": "https://documentation.wazuh.com/4.8/user-manual/reference/ossec-conf/office365-module.html", + "href": "https://documentation.wazuh.com/4.9/user-manual/reference/ossec-conf/office365-module.html", "text": "Configuration options for the module", }, ] diff --git a/plugins/main/public/controllers/management/components/management/configuration/office365/components/api-auth-tab/__snapshots__/api-auth-tab.test.tsx.snap b/plugins/main/public/controllers/management/components/management/configuration/office365/components/api-auth-tab/__snapshots__/api-auth-tab.test.tsx.snap index 9f5afff765..4d24e0d32d 100644 --- a/plugins/main/public/controllers/management/components/management/configuration/office365/components/api-auth-tab/__snapshots__/api-auth-tab.test.tsx.snap +++ b/plugins/main/public/controllers/management/components/management/configuration/office365/components/api-auth-tab/__snapshots__/api-auth-tab.test.tsx.snap @@ -41,11 +41,11 @@ exports[`ApiAuthTab component renders correctly to match the snapshot 1`] = ` help={ Array [ Object { - "href": "https://documentation.wazuh.com/4.8/cloud-security/office365/index.html", + "href": "https://documentation.wazuh.com/4.9/cloud-security/office365/index.html", "text": "Using Wazuh to monitor Office 365", }, Object { - "href": "https://documentation.wazuh.com/4.8/user-manual/reference/ossec-conf/office365-module.html", + "href": "https://documentation.wazuh.com/4.9/user-manual/reference/ossec-conf/office365-module.html", "text": "Configuration options for the module", }, ] @@ -92,11 +92,11 @@ exports[`ApiAuthTab component renders correctly to match the snapshot 1`] = ` links={ Array [ Object { - "href": "https://documentation.wazuh.com/4.8/cloud-security/office365/index.html", + "href": "https://documentation.wazuh.com/4.9/cloud-security/office365/index.html", "text": "Using Wazuh to monitor Office 365", }, Object { - "href": "https://documentation.wazuh.com/4.8/user-manual/reference/ossec-conf/office365-module.html", + "href": "https://documentation.wazuh.com/4.9/user-manual/reference/ossec-conf/office365-module.html", "text": "Configuration options for the module", }, ] diff --git a/plugins/main/public/controllers/management/components/management/configuration/office365/components/general-tab/__snapshots__/general-tab.test.tsx.snap b/plugins/main/public/controllers/management/components/management/configuration/office365/components/general-tab/__snapshots__/general-tab.test.tsx.snap index 4747759679..c3a7836d15 100644 --- a/plugins/main/public/controllers/management/components/management/configuration/office365/components/general-tab/__snapshots__/general-tab.test.tsx.snap +++ b/plugins/main/public/controllers/management/components/management/configuration/office365/components/general-tab/__snapshots__/general-tab.test.tsx.snap @@ -37,11 +37,11 @@ exports[`GeneralTab component renders correctly to match the snapshot 1`] = ` help={ Array [ Object { - "href": "https://documentation.wazuh.com/4.8/cloud-security/office365/index.html", + "href": "https://documentation.wazuh.com/4.9/cloud-security/office365/index.html", "text": "Using Wazuh to monitor Office 365", }, Object { - "href": "https://documentation.wazuh.com/4.8/user-manual/reference/ossec-conf/office365-module.html", + "href": "https://documentation.wazuh.com/4.9/user-manual/reference/ossec-conf/office365-module.html", "text": "Configuration options for the module", }, ] @@ -106,11 +106,11 @@ exports[`GeneralTab component renders correctly to match the snapshot 1`] = ` links={ Array [ Object { - "href": "https://documentation.wazuh.com/4.8/cloud-security/office365/index.html", + "href": "https://documentation.wazuh.com/4.9/cloud-security/office365/index.html", "text": "Using Wazuh to monitor Office 365", }, Object { - "href": "https://documentation.wazuh.com/4.8/user-manual/reference/ossec-conf/office365-module.html", + "href": "https://documentation.wazuh.com/4.9/user-manual/reference/ossec-conf/office365-module.html", "text": "Configuration options for the module", }, ] From 7a9a72b99556920b27ff863a7e26f08699017722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:38:30 +0200 Subject: [PATCH 002/136] Bump tough-cookie and @cypress/request in /plugins/main/test/cypress (#6030) Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) and [@cypress/request](https://github.com/cypress-io/request). These dependencies needed to be updated together. Updates `tough-cookie` from 2.5.0 to 4.1.3 - [Release notes](https://github.com/salesforce/tough-cookie/releases) - [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md) - [Commits](https://github.com/salesforce/tough-cookie/compare/v2.5.0...v4.1.3) Updates `@cypress/request` from 2.88.10 to 2.88.12 - [Release notes](https://github.com/cypress-io/request/releases) - [Changelog](https://github.com/cypress-io/request/blob/master/CHANGELOG.md) - [Commits](https://github.com/cypress-io/request/compare/v2.88.10...v2.88.12) --- updated-dependencies: - dependency-name: tough-cookie dependency-type: indirect - dependency-name: "@cypress/request" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- plugins/main/test/cypress/package-lock.json | 140 +++++++++++++++----- 1 file changed, 106 insertions(+), 34 deletions(-) diff --git a/plugins/main/test/cypress/package-lock.json b/plugins/main/test/cypress/package-lock.json index 870e56b8df..705ac87fd6 100644 --- a/plugins/main/test/cypress/package-lock.json +++ b/plugins/main/test/cypress/package-lock.json @@ -1891,9 +1891,9 @@ } }, "node_modules/@cypress/request": { - "version": "2.88.10", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", - "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "version": "2.88.12", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz", + "integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==", "dev": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -1909,9 +1909,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.5.2", + "qs": "~6.10.3", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -7214,12 +7214,18 @@ "dev": true }, "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/querystring": { @@ -7241,6 +7247,12 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7483,6 +7495,12 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -8190,27 +8208,38 @@ } }, "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=0.8" + "node": ">=6" } }, "node_modules/tough-cookie/node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, "engines": { "node": ">=6" } }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -8447,6 +8476,16 @@ "querystring": "0.2.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", @@ -10195,9 +10234,9 @@ } }, "@cypress/request": { - "version": "2.88.10", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", - "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "version": "2.88.12", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz", + "integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -10213,9 +10252,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.5.2", + "qs": "~6.10.3", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" } @@ -14381,10 +14420,13 @@ "dev": true }, "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } }, "querystring": { "version": "0.2.0", @@ -14398,6 +14440,12 @@ "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", "dev": true }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -14594,6 +14642,12 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -15146,19 +15200,27 @@ } }, "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "dependencies": { "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true } } @@ -15348,6 +15410,16 @@ } } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", From 7c04036ff337385ddb98cf4b4902066beeded6af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:44:33 +0200 Subject: [PATCH 003/136] Bump @babel/traverse from 7.18.13 to 7.23.2 in /plugins/main/test/cypress (#6032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump @babel/traverse in /plugins/main/test/cypress Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.18.13 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ɓlex Ruiz --- plugins/main/test/cypress/package-lock.json | 386 +++++++++++++------- 1 file changed, 252 insertions(+), 134 deletions(-) diff --git a/plugins/main/test/cypress/package-lock.json b/plugins/main/test/cypress/package-lock.json index 705ac87fd6..620f56f100 100644 --- a/plugins/main/test/cypress/package-lock.json +++ b/plugins/main/test/cypress/package-lock.json @@ -40,17 +40,80 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.18.13", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz", @@ -100,13 +163,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.13", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -243,9 +307,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -264,25 +328,25 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -411,30 +475,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -479,13 +543,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -555,9 +619,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1803,33 +1867,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1838,13 +1902,13 @@ } }, "node_modules/@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2096,13 +2160,13 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@nodelib/fs.scandir": { @@ -8931,12 +8995,65 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { @@ -8977,13 +9094,14 @@ } }, "@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.18.13", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "dependencies": { @@ -9087,9 +9205,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-explode-assignable-expression": { @@ -9102,22 +9220,22 @@ } }, "@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -9213,24 +9331,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -9263,13 +9381,13 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -9326,9 +9444,9 @@ } }, "@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -10161,42 +10279,42 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -10396,13 +10514,13 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@nodelib/fs.scandir": { From 8a410249db4a7c2bd5e624a9e75022cb6da1c729 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:57:39 +0200 Subject: [PATCH 004/136] Bump the npm_and_yarn at /plugins/main/test/cypress security update group in /plugins/main/test/cypress with 1 update (#6033) Bump the npm_and_yarn at /plugins/main/test/cypress security update group Bumps the npm_and_yarn at /plugins/main/test/cypress security update group in /plugins/main/test/cypress with 1 update: [cypress](https://github.com/cypress-io/cypress). - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v9.7.0...v13.3.2) --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- plugins/main/test/cypress/package-lock.json | 139 ++++++++++---------- plugins/main/test/cypress/package.json | 2 +- 2 files changed, 73 insertions(+), 68 deletions(-) diff --git a/plugins/main/test/cypress/package-lock.json b/plugins/main/test/cypress/package-lock.json index 620f56f100..3d39259d39 100644 --- a/plugins/main/test/cypress/package-lock.json +++ b/plugins/main/test/cypress/package-lock.json @@ -14,7 +14,7 @@ "prettier": "^2.3.0" }, "devDependencies": { - "cypress": "^9.5.2", + "cypress": "^13.3.2", "cypress-cucumber-preprocessor": "^4.0.3", "cypress-real-events": "^1.7.1", "cypress-xpath": "^1.6.2", @@ -1955,9 +1955,9 @@ } }, "node_modules/@cypress/request": { - "version": "2.88.12", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz", - "integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -1973,7 +1973,7 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.10.3", + "qs": "6.10.4", "safe-buffer": "^5.1.2", "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", @@ -2208,9 +2208,9 @@ } }, "node_modules/@types/node": { - "version": "14.18.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.26.tgz", - "integrity": "sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "node_modules/@types/sinonjs__fake-timers": { @@ -2564,9 +2564,9 @@ } }, "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, "node_modules/babel-plugin-add-module-exports": { @@ -3362,9 +3362,9 @@ } }, "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, "engines": { "node": ">= 6" @@ -3645,15 +3645,15 @@ } }, "node_modules/cypress": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.7.0.tgz", - "integrity": "sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.2.tgz", + "integrity": "sha512-ArLmZObcLC+xxCp7zJZZbhby9FUf5CueLej9dUM4+5j37FTS4iMSgHxQLDu01PydFUvDXcNoIVRCYrHHxD7Ybg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@cypress/request": "^2.88.10", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", + "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", @@ -3665,12 +3665,12 @@ "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", - "debug": "^4.3.2", + "debug": "^4.3.4", "enquirer": "^2.3.6", - "eventemitter2": "^6.4.3", + "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", @@ -3683,12 +3683,13 @@ "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.3.2", + "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", @@ -3698,7 +3699,7 @@ "cypress": "bin/cypress" }, "engines": { - "node": ">=12.0.0" + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, "node_modules/cypress-cucumber-preprocessor": { @@ -6250,10 +6251,13 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mkdirp-classic": { "version": "0.5.3", @@ -7712,9 +7716,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -7887,9 +7891,9 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "dependencies": { "asn1": "~0.2.3", @@ -10352,9 +10356,9 @@ } }, "@cypress/request": { - "version": "2.88.12", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz", - "integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -10370,7 +10374,7 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.10.3", + "qs": "6.10.4", "safe-buffer": "^5.1.2", "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", @@ -10553,9 +10557,9 @@ } }, "@types/node": { - "version": "14.18.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.26.tgz", - "integrity": "sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==", + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", "dev": true }, "@types/sinonjs__fake-timers": { @@ -10831,9 +10835,9 @@ "dev": true }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, "babel-plugin-add-module-exports": { @@ -11471,9 +11475,9 @@ } }, "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true }, "common-tags": { @@ -11722,14 +11726,14 @@ "dev": true }, "cypress": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.7.0.tgz", - "integrity": "sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.2.tgz", + "integrity": "sha512-ArLmZObcLC+xxCp7zJZZbhby9FUf5CueLej9dUM4+5j37FTS4iMSgHxQLDu01PydFUvDXcNoIVRCYrHHxD7Ybg==", "dev": true, "requires": { - "@cypress/request": "^2.88.10", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", + "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", @@ -11741,12 +11745,12 @@ "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", - "debug": "^4.3.2", + "debug": "^4.3.4", "enquirer": "^2.3.6", - "eventemitter2": "^6.4.3", + "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", @@ -11759,12 +11763,13 @@ "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.3.2", + "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", @@ -13734,9 +13739,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "mkdirp-classic": { @@ -14863,9 +14868,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -14994,9 +14999,9 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "requires": { "asn1": "~0.2.3", diff --git a/plugins/main/test/cypress/package.json b/plugins/main/test/cypress/package.json index 7f153c5667..5b88b71d8c 100644 --- a/plugins/main/test/cypress/package.json +++ b/plugins/main/test/cypress/package.json @@ -34,7 +34,7 @@ "prettier": "^2.3.0" }, "devDependencies": { - "cypress": "^9.5.2", + "cypress": "^13.3.2", "cypress-cucumber-preprocessor": "^4.0.3", "cypress-real-events": "^1.7.1", "cypress-xpath": "^1.6.2", From 9a5857701c2e60ac7ac8b1637b0ad821cfe0dd33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:53:50 -0300 Subject: [PATCH 005/136] Bump crypto-js from 4.1.1 to 4.2.0 in /plugins/main (#6062) Bumps [crypto-js](https://github.com/brix/crypto-js) from 4.1.1 to 4.2.0. - [Commits](https://github.com/brix/crypto-js/compare/4.1.1...4.2.0) --- updated-dependencies: - dependency-name: crypto-js dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- plugins/main/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/main/yarn.lock b/plugins/main/yarn.lock index 3db28ae6c7..c30706356d 100644 --- a/plugins/main/yarn.lock +++ b/plugins/main/yarn.lock @@ -1094,9 +1094,9 @@ crypt@0.0.2: integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== crypto-js@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" - integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== csstype@^3.0.2: version "3.1.2" From 37bd379f0aef3494fea32afa641dfe02ca67e1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Mon, 30 Oct 2023 11:59:53 +0100 Subject: [PATCH 006/136] Rename compatibilty_request.md to compatibility_request.md --- .../{compatibilty_request.md => compatibility_request.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{compatibilty_request.md => compatibility_request.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/compatibilty_request.md b/.github/ISSUE_TEMPLATE/compatibility_request.md similarity index 100% rename from .github/ISSUE_TEMPLATE/compatibilty_request.md rename to .github/ISSUE_TEMPLATE/compatibility_request.md From 61418969c1b4a52ec2dedcde63787b90e5f0896d Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Wed, 22 Nov 2023 11:01:03 +0100 Subject: [PATCH 007/136] Bump version 4.8.1 rev 00 (#6140) --- CHANGELOG.md | 6 + plugins/main/common/api-info/endpoints.json | 929 +++++++++++++----- .../common/api-info/security-actions.json | 853 ++++++++++++---- plugins/main/opensearch_dashboards.json | 15 +- plugins/main/package.json | 2 +- .../opensearch_dashboards.json | 2 +- plugins/wazuh-check-updates/package.json | 2 +- plugins/wazuh-core/opensearch_dashboards.json | 2 +- plugins/wazuh-core/package.json | 2 +- 9 files changed, 1360 insertions(+), 453 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1143785ebd..37854a786d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to the Wazuh app project will be documented in this file. +## Wazuh v4.8.1 - OpenSearch Dashboards 2.10.0 - Revision 00 + +### Added + +- Support for Wazuh 4.8.1 + ## Wazuh v4.8.0 - OpenSearch Dashboards 2.10.0 - Revision 00 ### Added diff --git a/plugins/main/common/api-info/endpoints.json b/plugins/main/common/api-info/endpoints.json index 440a61fcf6..b36353c39f 100644 --- a/plugins/main/common/api-info/endpoints.json +++ b/plugins/main/common/api-info/endpoints.json @@ -7,7 +7,9 @@ "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", @@ -24,7 +26,9 @@ "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", @@ -61,7 +65,10 @@ "description": "Agent groups configuration sync status", "schema": { "type": "string", - "enum": ["synced", "not synced"] + "enum": [ + "synced", + "not synced" + ] } }, { @@ -206,7 +213,12 @@ "type": "array", "items": { "type": "string", - "enum": ["active", "pending", "never_connected", "disconnected"] + "enum": [ + "active", + "pending", + "never_connected", + "disconnected" + ] }, "minItems": 1 } @@ -234,7 +246,9 @@ "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", @@ -332,7 +346,9 @@ "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", @@ -354,7 +370,10 @@ "type": "array", "items": { "type": "string", - "enum": ["wazuh-analysisd", "wazuh-remoted"] + "enum": [ + "wazuh-analysisd", + "wazuh-remoted" + ] } } }, @@ -381,7 +400,9 @@ "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", @@ -419,7 +440,9 @@ "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", @@ -457,7 +480,9 @@ "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", @@ -476,7 +501,10 @@ "required": true, "schema": { "type": "string", - "enum": ["logcollector", "agent"] + "enum": [ + "logcollector", + "agent" + ] } } ], @@ -504,7 +532,9 @@ "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", @@ -584,7 +614,9 @@ "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", @@ -653,7 +685,9 @@ "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", @@ -733,7 +767,9 @@ "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", @@ -758,7 +794,9 @@ "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", @@ -783,7 +821,9 @@ "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", @@ -909,7 +949,9 @@ "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", @@ -1071,7 +1113,9 @@ "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", @@ -1163,7 +1207,9 @@ "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", @@ -1259,7 +1305,9 @@ "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", @@ -1279,7 +1327,11 @@ "type": "array", "items": { "type": "string", - "enum": ["wazuh-analysisd", "wazuh-remoted", "wazuh-db"] + "enum": [ + "wazuh-analysisd", + "wazuh-remoted", + "wazuh-db" + ] } } }, @@ -1306,7 +1358,9 @@ "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", @@ -1342,7 +1396,9 @@ "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", @@ -1464,7 +1520,9 @@ "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", @@ -1500,7 +1558,9 @@ "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", @@ -1544,7 +1604,9 @@ "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", @@ -1580,7 +1642,9 @@ "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", @@ -1616,7 +1680,9 @@ "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", @@ -1652,7 +1718,9 @@ "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", @@ -1688,7 +1756,9 @@ "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", @@ -1724,7 +1794,9 @@ "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", @@ -1759,7 +1831,9 @@ "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", @@ -1794,7 +1868,9 @@ "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", @@ -1829,7 +1905,9 @@ "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", @@ -1854,7 +1932,9 @@ "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", @@ -1879,7 +1959,9 @@ "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", @@ -1967,7 +2049,10 @@ "description": "Filter by node type", "schema": { "type": "string", - "enum": ["worker", "master"] + "enum": [ + "worker", + "master" + ] } }, { @@ -1985,7 +2070,9 @@ "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", @@ -2020,7 +2107,9 @@ "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", @@ -2045,7 +2134,9 @@ "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", @@ -2153,7 +2244,11 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": ["enabled", "disabled", "all"], + "enum": [ + "enabled", + "disabled", + "all" + ], "minItems": 1 } }, @@ -2172,7 +2267,9 @@ "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", @@ -2269,7 +2366,11 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": ["enabled", "disabled", "all"], + "enum": [ + "enabled", + "disabled", + "all" + ], "minItems": 1 } }, @@ -2288,7 +2389,9 @@ "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", @@ -2340,7 +2443,9 @@ "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", @@ -2413,7 +2518,9 @@ "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", @@ -2568,7 +2675,9 @@ "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", @@ -2705,7 +2814,9 @@ "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", @@ -2798,7 +2909,9 @@ "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", @@ -2916,7 +3029,9 @@ "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", @@ -3115,7 +3230,9 @@ "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", @@ -3136,7 +3253,12 @@ "schema": { "type": "string", "description": "DHCP status", - "enum": ["enabled", "disabled", "unknown", "BOOTP"] + "enum": [ + "enabled", + "disabled", + "unknown", + "BOOTP" + ] } }, { @@ -3234,7 +3356,9 @@ "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", @@ -3360,7 +3484,9 @@ "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", @@ -3484,7 +3610,9 @@ "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", @@ -3634,7 +3762,9 @@ "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", @@ -3832,7 +3962,9 @@ "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", @@ -3953,7 +4085,9 @@ "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", @@ -4045,7 +4179,12 @@ "type": "array", "items": { "type": "string", - "enum": ["active", "pending", "never_connected", "disconnected"] + "enum": [ + "active", + "pending", + "never_connected", + "disconnected" + ] }, "minItems": 1 } @@ -4065,7 +4204,9 @@ "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", @@ -4123,7 +4264,9 @@ "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", @@ -4244,7 +4387,9 @@ "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", @@ -4282,7 +4427,12 @@ "type": "array", "items": { "type": "string", - "enum": ["conf", "rootkit_files", "rootkit_trojans", "rcl"] + "enum": [ + "conf", + "rootkit_files", + "rootkit_trojans", + "rcl" + ] } } }, @@ -4301,7 +4451,9 @@ "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", @@ -4339,7 +4491,12 @@ "type": "array", "items": { "type": "string", - "enum": ["conf", "rootkit_files", "rootkit_trojans", "rcl"] + "enum": [ + "conf", + "rootkit_files", + "rootkit_trojans", + "rcl" + ] } } }, @@ -4358,7 +4515,9 @@ "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", @@ -4465,7 +4624,9 @@ "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", @@ -4546,7 +4707,9 @@ "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", @@ -4590,7 +4753,9 @@ "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", @@ -4615,7 +4780,9 @@ "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", @@ -4704,7 +4871,9 @@ "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", @@ -4791,7 +4960,9 @@ "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", @@ -4816,7 +4987,9 @@ "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", @@ -4825,7 +4998,11 @@ "type": "array", "items": { "type": "string", - "enum": ["wazuh-analysisd", "wazuh-remoted", "wazuh-db"] + "enum": [ + "wazuh-analysisd", + "wazuh-remoted", + "wazuh-db" + ] } } }, @@ -4852,7 +5029,9 @@ "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", @@ -4877,7 +5056,9 @@ "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", @@ -4988,7 +5169,9 @@ "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", @@ -5013,7 +5196,9 @@ "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", @@ -5046,7 +5231,9 @@ "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", @@ -5071,7 +5258,9 @@ "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", @@ -5096,7 +5285,9 @@ "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", @@ -5121,7 +5312,9 @@ "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", @@ -5146,32 +5339,9 @@ "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"], - "query": [ - { - "name": "pretty", - "description": "Show results in human-readable format", - "schema": { - "type": "boolean", - "default": false - } - }, - { - "name": "wait_for_complete", - "description": "Disable timeout response", - "schema": { - "type": "boolean", - "default": false - } - } - ] - }, - { - "name": "/manager/version/check", - "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api_controllers_manager_controller_get_available_updates", - "description": "Return the version of the API and the available updates", - "summary": "Get available updates", - "tags": ["Manager"], + "tags": [ + "Manager" + ], "query": [ { "name": "pretty", @@ -5196,7 +5366,9 @@ "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", @@ -5295,7 +5467,9 @@ "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", @@ -5320,7 +5494,9 @@ "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", @@ -5419,7 +5595,9 @@ "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", @@ -5510,7 +5688,9 @@ "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", @@ -5609,7 +5789,9 @@ "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", @@ -5708,7 +5890,9 @@ "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", @@ -5807,7 +5991,9 @@ "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", @@ -5832,7 +6018,9 @@ "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", @@ -5957,7 +6145,9 @@ "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", @@ -5995,7 +6185,9 @@ "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", @@ -6168,7 +6360,11 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": ["enabled", "disabled", "all"], + "enum": [ + "enabled", + "disabled", + "all" + ], "minItems": 1 } }, @@ -6195,7 +6391,9 @@ "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", @@ -6292,7 +6490,11 @@ "description": "Filter by list status. Use commas to enter multiple statuses", "schema": { "type": "string", - "enum": ["enabled", "disabled", "all"], + "enum": [ + "enabled", + "disabled", + "all" + ], "minItems": 1 } }, @@ -6311,7 +6513,9 @@ "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", @@ -6363,7 +6567,9 @@ "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", @@ -6425,7 +6631,9 @@ "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", @@ -6505,7 +6713,9 @@ "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", @@ -6628,7 +6838,9 @@ "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", @@ -6839,7 +7051,9 @@ "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", @@ -6863,7 +7077,9 @@ "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", @@ -6888,7 +7104,9 @@ "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", @@ -6988,7 +7206,9 @@ "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", @@ -7025,7 +7245,9 @@ "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", @@ -7125,7 +7347,9 @@ "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", @@ -7225,7 +7449,9 @@ "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", @@ -7242,7 +7468,9 @@ "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", @@ -7342,7 +7570,9 @@ "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", @@ -7367,7 +7597,9 @@ "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", @@ -7384,7 +7616,9 @@ "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", @@ -7404,7 +7638,10 @@ "description": "Filter by architecture", "schema": { "type": "string", - "enum": ["[x32]", "[x64]"] + "enum": [ + "[x32]", + "[x64]" + ] } }, { @@ -7531,7 +7768,11 @@ "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" + ] } }, { @@ -7565,7 +7806,9 @@ "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", @@ -7603,7 +7846,9 @@ "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", @@ -7652,7 +7897,9 @@ "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", @@ -7760,7 +8007,9 @@ "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", @@ -7901,7 +8150,9 @@ "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", @@ -8114,7 +8365,9 @@ "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", @@ -8135,15 +8388,12 @@ "schema": { "type": "string", "description": "DHCP status", - "enum": ["enabled", "disabled", "unknown", "BOOTP"] - } - }, - { - "name": "distinct", - "description": "Look for distinct values.", - "schema": { - "type": "boolean", - "default": false + "enum": [ + "enabled", + "disabled", + "unknown", + "BOOTP" + ] } }, { @@ -8256,7 +8506,9 @@ "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", @@ -8305,7 +8557,9 @@ "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", @@ -8444,7 +8698,9 @@ "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", @@ -8609,7 +8865,9 @@ "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", @@ -8822,7 +9080,9 @@ "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", @@ -8959,7 +9219,9 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.vulnerability_controller.get_vulnerability_agent", "description": "Return the vulnerabilities of an agent", "summary": "Get vulnerabilities", - "tags": ["Vulnerability"], + "tags": [ + "Vulnerability" + ], "args": [ { "name": ":agent_id", @@ -9082,7 +9344,11 @@ "description": "Filter by CVE status", "schema": { "type": "string", - "enum": ["valid", "pending", "obsolete"] + "enum": [ + "valid", + "pending", + "obsolete" + ] } }, { @@ -9090,7 +9356,10 @@ "description": "Filter by CVE type", "schema": { "type": "string", - "enum": ["os", "package"] + "enum": [ + "os", + "package" + ] } }, { @@ -9116,7 +9385,9 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.vulnerability_controller.get_last_scan_agent", "description": "Return when the last full and partial vulnerability scan of a specified agent ended.", "summary": "Get last scan datetime", - "tags": ["Vulnerability"], + "tags": [ + "Vulnerability" + ], "args": [ { "name": ":agent_id", @@ -9154,7 +9425,9 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.vulnerability_controller.get_vulnerabilities_field_summary", "description": "Return a summary of the vulnerabilities' field of an agent", "summary": "Get agent vulnerabilities' field summary", - "tags": ["Vulnerability"], + "tags": [ + "Vulnerability" + ], "args": [ { "name": ":agent_id", @@ -9233,7 +9506,9 @@ "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", @@ -9291,7 +9566,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } ] }, @@ -9300,7 +9577,9 @@ "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", @@ -9355,7 +9634,9 @@ "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", @@ -9393,7 +9674,9 @@ "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", @@ -9448,7 +9731,9 @@ "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", @@ -9485,7 +9770,9 @@ "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", @@ -9521,7 +9808,9 @@ "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", @@ -9559,7 +9848,9 @@ "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", @@ -9597,7 +9888,9 @@ "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", @@ -9756,7 +10049,9 @@ "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", @@ -9900,7 +10195,9 @@ "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", @@ -9936,7 +10233,9 @@ "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", @@ -9971,7 +10270,9 @@ "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", @@ -10023,7 +10324,9 @@ "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", @@ -10060,7 +10363,9 @@ "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", @@ -10104,7 +10409,9 @@ "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", @@ -10126,7 +10433,11 @@ "body": [ { "type": "object", - "required": ["event", "log_format", "location"], + "required": [ + "event", + "log_format", + "location" + ], "properties": { "token": { "type": "string", @@ -10153,7 +10464,9 @@ "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", @@ -10178,7 +10491,9 @@ "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", @@ -10203,7 +10518,9 @@ "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", @@ -10241,7 +10558,9 @@ "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", @@ -10293,7 +10612,9 @@ "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", @@ -10327,7 +10648,10 @@ "rbac_mode": { "description": "RBAC mode (white/black)", "type": "string", - "enum": ["white", "black"], + "enum": [ + "white", + "black" + ], "example": "white" } } @@ -10339,7 +10663,9 @@ "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", @@ -10403,7 +10729,11 @@ "description": "Effect of the policy" } }, - "required": ["actions", "resources", "effect"] + "required": [ + "actions", + "resources", + "effect" + ] } } } @@ -10414,7 +10744,9 @@ "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", @@ -10464,7 +10796,9 @@ "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", @@ -10518,14 +10852,18 @@ "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", @@ -10573,7 +10911,9 @@ "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", @@ -10618,7 +10958,9 @@ "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", @@ -10656,7 +10998,9 @@ "documentation": "https://documentation.wazuh.com/4.8/user-manual/api/reference.html#operation/api.controllers.vulnerability_controller.run_vulnerability_scan", "description": "Run a vulnerability detector scan in all nodes", "summary": "Run vulnerability detector scan", - "tags": ["Vulnerability"], + "tags": [ + "Vulnerability" + ], "query": [ { "name": "pretty", @@ -10686,7 +11030,9 @@ "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", @@ -10720,7 +11066,9 @@ "format": "alphanumeric" } }, - "required": ["name"] + "required": [ + "name" + ] } ] }, @@ -10729,7 +11077,9 @@ "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", @@ -10809,7 +11159,9 @@ } } }, - "required": ["name"] + "required": [ + "name" + ] } ] }, @@ -10818,7 +11170,9 @@ "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", @@ -10853,7 +11207,9 @@ "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", @@ -10884,7 +11240,9 @@ } } }, - "required": ["events"] + "required": [ + "events" + ] } ] }, @@ -10893,7 +11251,9 @@ "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", @@ -10923,7 +11283,9 @@ "maxLength": 128 } }, - "required": ["group_id"] + "required": [ + "group_id" + ] } ] }, @@ -10932,7 +11294,9 @@ "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", @@ -10954,7 +11318,10 @@ "body": [ { "type": "object", - "required": ["name", "policy"], + "required": [ + "name", + "policy" + ], "properties": { "name": { "description": "Policy name", @@ -10985,7 +11352,11 @@ "description": "Effect of the policy" } }, - "required": ["actions", "resources", "effect"] + "required": [ + "actions", + "resources", + "effect" + ] } } } @@ -10996,7 +11367,9 @@ "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", @@ -11018,7 +11391,9 @@ "body": [ { "type": "object", - "required": ["name"], + "required": [ + "name" + ], "properties": { "name": { "type": "string", @@ -11035,7 +11410,9 @@ "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", @@ -11094,7 +11471,9 @@ "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", @@ -11144,7 +11523,9 @@ "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", @@ -11166,7 +11547,10 @@ "body": [ { "type": "object", - "required": ["name", "rule"], + "required": [ + "name", + "rule" + ], "properties": { "name": { "type": "string", @@ -11187,7 +11571,9 @@ "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", @@ -11204,7 +11590,9 @@ "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", @@ -11221,7 +11609,9 @@ "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", @@ -11255,7 +11645,10 @@ "format": "password" } }, - "required": ["username", "password"] + "required": [ + "username", + "password" + ] } ] }, @@ -11264,7 +11657,9 @@ "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", @@ -11328,7 +11723,9 @@ "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", @@ -11491,7 +11888,9 @@ "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", @@ -11541,7 +11940,9 @@ "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", @@ -11589,7 +11990,9 @@ "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", @@ -11638,7 +12041,9 @@ "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", @@ -11682,7 +12087,9 @@ "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", @@ -11721,7 +12128,9 @@ "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", @@ -11760,7 +12169,9 @@ "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", @@ -11799,7 +12210,9 @@ "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", @@ -11835,7 +12248,9 @@ "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", @@ -11871,7 +12286,9 @@ "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", @@ -11909,7 +12326,9 @@ "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", @@ -11953,7 +12372,9 @@ "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", @@ -11978,7 +12399,9 @@ "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", @@ -12016,7 +12439,9 @@ "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", @@ -12054,7 +12479,9 @@ "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", @@ -12104,7 +12531,9 @@ "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", @@ -12154,7 +12583,9 @@ "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", @@ -12192,14 +12623,18 @@ "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", @@ -12237,7 +12672,9 @@ "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", @@ -12287,7 +12724,9 @@ "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", @@ -12322,4 +12761,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 418600f6c4..37d13fe040 100644 --- a/plugins/main/common/api-info/security-actions.json +++ b/plugins/main/common/api-info/security-actions.json @@ -1,30 +1,57 @@ { "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": [ @@ -45,20 +72,38 @@ }, "agent:create": { "description": "Create new agents", - "resources": ["*:*"], + "resources": [ + "*:*" + ], "example": { - "actions": ["agent:create"], - "resources": ["*:*:*"], + "actions": [ + "agent:create" + ], + "resources": [ + "*:*:*" + ], "effect": "allow" }, - "related_endpoints": ["POST /agents", "POST /agents/insert", "POST /agents/insert/quick"] + "related_endpoints": [ + "POST /agents", + "POST /agents/insert", + "POST /agents/insert/quick" + ] }, "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": [ @@ -71,10 +116,16 @@ }, "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": [ @@ -87,10 +138,18 @@ }, "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": [ @@ -102,10 +161,18 @@ }, "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": [ @@ -116,20 +183,34 @@ }, "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": [ @@ -144,30 +225,53 @@ }, "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": [ @@ -195,110 +299,207 @@ }, "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": ["GET /ciscat/{agent_id}/results", "GET /experimental/ciscat/results"] + "related_endpoints": [ + "GET /ciscat/{agent_id}/results", + "GET /experimental/ciscat/results" + ] }, "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": ["GET /lists", "GET /lists/files/{filename}", "GET /lists/files"] + "related_endpoints": [ + "GET /lists", + "GET /lists/files/{filename}", + "GET /lists/files" + ] }, "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": ["PUT /lists/files/{filename}", "DELETE /lists/files/{filename}"] + "related_endpoints": [ + "PUT /lists/files/{filename}", + "DELETE /lists/files/{filename}" + ] }, "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": [ @@ -315,46 +516,75 @@ "GET /manager/logs/summary", "PUT /manager/restart", "GET /manager/configuration/validation", - "GET /manager/configuration/{component}/{configuration}", - "GET /manager/version/check" + "GET /manager/configuration/{component}/{configuration}" ] }, "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": [ @@ -369,40 +599,75 @@ }, "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": ["GET /rootcheck/{agent_id}", "GET /rootcheck/{agent_id}/last_scan"] + "related_endpoints": [ + "GET /rootcheck/{agent_id}", + "GET /rootcheck/{agent_id}/last_scan" + ] }, "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": ["DELETE /rootcheck/{agent_id}", "DELETE /experimental/rootcheck"] + "related_endpoints": [ + "DELETE /rootcheck/{agent_id}", + "DELETE /experimental/rootcheck" + ] }, "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": [ @@ -415,70 +680,133 @@ }, "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": ["PUT /rules/files/{filename}", "DELETE /rules/files/{filename}"] + "related_endpoints": [ + "PUT /rules/files/{filename}", + "DELETE /rules/files/{filename}" + ] }, "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": ["GET /sca/{agent_id}", "GET /sca/{agent_id}/checks/{policy_id}"] + "related_endpoints": [ + "GET /sca/{agent_id}", + "GET /sca/{agent_id}/checks/{policy_id}" + ] }, "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": ["GET /syscheck/{agent_id}", "GET /syscheck/{agent_id}/last_scan"] + "related_endpoints": [ + "GET /syscheck/{agent_id}", + "GET /syscheck/{agent_id}/last_scan" + ] }, "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": ["DELETE /syscheck/{agent_id}", "DELETE /experimental/syscheck"] + "related_endpoints": [ + "DELETE /syscheck/{agent_id}", + "DELETE /experimental/syscheck" + ] }, "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": [ @@ -490,30 +818,54 @@ }, "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": ["PUT /decoders/files/{filename}", "DELETE /decoders/files/{filename}"] + "related_endpoints": [ + "PUT /decoders/files/{filename}", + "DELETE /decoders/files/{filename}" + ] }, "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": [ @@ -539,20 +891,40 @@ }, "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": [ @@ -564,20 +936,40 @@ }, "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": [ @@ -592,10 +984,22 @@ }, "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": [ @@ -610,60 +1014,111 @@ }, "security:create": { "description": "Create new system security resources", - "resources": ["*:*"], + "resources": [ + "*:*" + ], "example": { - "actions": ["security:create"], - "resources": ["*:*:*"], + "actions": [ + "security:create" + ], + "resources": [ + "*:*:*" + ], "effect": "deny" }, - "related_endpoints": ["POST /security/roles", "POST /security/rules", "POST /security/policies"] + "related_endpoints": [ + "POST /security/roles", + "POST /security/rules", + "POST /security/policies" + ] }, "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" + ] }, "vulnerability:run": { "description": "Allow running a vulnerability detector scan", - "resources": ["*:*"], + "resources": [ + "*:*" + ], "example": { - "actions": ["vulnerability:run"], - "resources": ["*:*:*"], + "actions": [ + "vulnerability:run" + ], + "resources": [ + "*:*:*" + ], "effect": "allow" }, - "related_endpoints": ["PUT /vulnerability"] + "related_endpoints": [ + "PUT /vulnerability" + ] }, "vulnerability:read": { "description": "Allow reading agents' vulnerabilities information", - "resources": ["agent:id", "agent:group"], + "resources": [ + "agent:id", + "agent:group" + ], "example": { - "actions": ["vulnerability:read"], - "resources": ["agent:id:011", "agent:group:us-west"], + "actions": [ + "vulnerability:read" + ], + "resources": [ + "agent:id:011", + "agent:group:us-west" + ], "effect": "allow" }, "related_endpoints": [ @@ -690,4 +1145,4 @@ "POST /events" ] } -} +} \ No newline at end of file diff --git a/plugins/main/opensearch_dashboards.json b/plugins/main/opensearch_dashboards.json index 744edec00f..37b654d045 100644 --- a/plugins/main/opensearch_dashboards.json +++ b/plugins/main/opensearch_dashboards.json @@ -1,8 +1,10 @@ { "id": "wazuh", - "version": "4.8.0-00", + "version": "4.8.1-00", "opensearchDashboardsVersion": "opensearchDashboards", - "configPath": ["wazuh"], + "configPath": [ + "wazuh" + ], "requiredPlugins": [ "navigation", "data", @@ -19,7 +21,12 @@ "opensearchDashboardsLegacy", "wazuhCheckUpdates" ], - "optionalPlugins": ["security", "securityDashboards", "searchguard", "telemetry"], + "optionalPlugins": [ + "security", + "securityDashboards", + "searchguard", + "telemetry" + ], "server": true, "ui": true -} +} \ No newline at end of file diff --git a/plugins/main/package.json b/plugins/main/package.json index 12693996bc..c8152d661b 100644 --- a/plugins/main/package.json +++ b/plugins/main/package.json @@ -1,6 +1,6 @@ { "name": "wazuh", - "version": "4.8.0", + "version": "4.8.1", "revision": "00", "pluginPlatform": { "version": "2.10.0" diff --git a/plugins/wazuh-check-updates/opensearch_dashboards.json b/plugins/wazuh-check-updates/opensearch_dashboards.json index fc816ab6d5..112f05fa5a 100644 --- a/plugins/wazuh-check-updates/opensearch_dashboards.json +++ b/plugins/wazuh-check-updates/opensearch_dashboards.json @@ -1,6 +1,6 @@ { "id": "wazuhCheckUpdates", - "version": "4.8.0-00", + "version": "4.8.1-00", "opensearchDashboardsVersion": "opensearchDashboards", "server": true, "ui": true, diff --git a/plugins/wazuh-check-updates/package.json b/plugins/wazuh-check-updates/package.json index b6655d602a..106451ed81 100644 --- a/plugins/wazuh-check-updates/package.json +++ b/plugins/wazuh-check-updates/package.json @@ -1,6 +1,6 @@ { "name": "wazuh-check-updates", - "version": "4.8.0", + "version": "4.8.1", "revision": "00", "pluginPlatform": { "version": "2.10.0" diff --git a/plugins/wazuh-core/opensearch_dashboards.json b/plugins/wazuh-core/opensearch_dashboards.json index 33b39ce69f..a68490fc52 100644 --- a/plugins/wazuh-core/opensearch_dashboards.json +++ b/plugins/wazuh-core/opensearch_dashboards.json @@ -1,6 +1,6 @@ { "id": "wazuhCore", - "version": "4.8.0-00", + "version": "4.8.1-00", "opensearchDashboardsVersion": "opensearchDashboards", "server": true, "ui": true, diff --git a/plugins/wazuh-core/package.json b/plugins/wazuh-core/package.json index 29db1c64c6..e0a811f4f9 100644 --- a/plugins/wazuh-core/package.json +++ b/plugins/wazuh-core/package.json @@ -1,6 +1,6 @@ { "name": "wazuh-core", - "version": "4.8.0", + "version": "4.8.1", "revision": "00", "pluginPlatform": { "version": "2.10.0" From f5dbad24a0c897fa70f37e5e29a94e0b1d943103 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Thu, 30 Nov 2023 19:00:06 +0100 Subject: [PATCH 008/136] Add angularJS dependencies (#6145) * Add angularJS dependencies and fix unit tests * Add changelog --- CHANGELOG.md | 1 + docker/osd-dev/dev.sh | 2 + plugins/main/package.json | 8 +- .../common/modules/discover/discover.tsx | 193 +++++--- plugins/main/public/get_inner_angular.ts | 34 +- .../kibana-integrations/kibana-discover.js | 4 +- .../packages/osd-i18n/angular/directive.ts | 126 +++++ .../packages/osd-i18n/angular/filter.ts | 42 ++ .../packages/osd-i18n/angular/index.ts | 38 ++ .../packages/osd-i18n/angular/provider.ts | 48 ++ .../packages/osd-i18n/core/formats.ts | 163 +++++++ .../packages/osd-i18n/core/helper.ts | 56 +++ .../packages/osd-i18n/core/i18n.ts | 265 +++++++++++ .../packages/osd-i18n/core/index.ts | 32 ++ .../packages/osd-i18n/core/locales.js | 40 ++ .../packages/osd-i18n/core/pseudo_locale.ts | 115 +++++ .../public/angular/angular_config.tsx | 380 +++++++++++++++ .../public/angular/index.ts | 38 ++ .../public/angular/osd_top_nav.js | 140 ++++++ .../public/angular/promises.js | 140 ++++++ .../angular/subscribe_with_scope.test.ts | 208 +++++++++ .../public/angular/subscribe_with_scope.ts | 96 ++++ .../public/angular/watch_multi.js | 159 +++++++ .../angular_bootstrap/bind_html/bind_html.js | 28 ++ .../public/angular_bootstrap/index.ts | 61 +++ .../angular_bootstrap/tooltip/position.js | 178 +++++++ .../angular_bootstrap/tooltip/tooltip.js | 434 ++++++++++++++++++ .../tooltip/tooltip_html_unsafe_popup.html | 4 + .../tooltip/tooltip_popup.html | 4 + .../public/index.ts | 34 ++ .../public/notify/index.ts | 32 ++ .../public/notify/lib/add_fatal_error.ts | 49 ++ .../notify/lib/format_angular_http_error.ts | 69 +++ .../public/notify/lib/format_msg.test.js | 90 ++++ .../public/notify/lib/format_msg.ts | 91 ++++ .../notify/lib/format_opensearch_msg.test.js | 87 ++++ .../notify/lib/format_opensearch_msg.ts | 48 ++ .../public/notify/lib/format_stack.ts | 46 ++ .../public/notify/lib/index.ts | 39 ++ .../notify/toasts/TOAST_NOTIFICATIONS.md | 100 ++++ .../public/notify/toasts/index.ts | 31 ++ .../notify/toasts/toast_notifications.ts | 64 +++ .../public/utils/index.ts | 37 ++ .../public/utils/inject_header_style.ts | 54 +++ .../public/utils/osd_accessible_click.js | 82 ++++ .../public/utils/private.d.ts | 31 ++ .../public/utils/private.js | 214 +++++++++ .../utils/register_listen_event_listener.js | 47 ++ .../public/utils/system_api.ts | 62 +++ plugins/main/yarn.lock | 30 ++ .../opensearch_dashboards.json | 4 +- plugins/wazuh-check-updates/package.json | 6 +- plugins/wazuh-check-updates/yarn.lock | 8 +- plugins/wazuh-core/opensearch_dashboards.json | 4 +- plugins/wazuh-core/package.json | 6 +- plugins/wazuh-core/yarn.lock | 8 +- 56 files changed, 4312 insertions(+), 98 deletions(-) create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/angular/directive.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/angular/filter.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/angular/index.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/angular/provider.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/core/formats.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/core/helper.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/core/i18n.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/core/index.ts create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/core/locales.js create mode 100644 plugins/main/public/kibana-integrations/packages/osd-i18n/core/pseudo_locale.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/angular_config.tsx create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/index.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/osd_top_nav.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/promises.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.test.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/watch_multi.js create mode 100755 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/bind_html/bind_html.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/index.ts create mode 100755 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/position.js create mode 100755 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_html_unsafe_popup.html create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_popup.html create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/index.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/index.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/add_fatal_error.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_angular_http_error.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.test.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.test.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_stack.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/index.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/index.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/toast_notifications.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/index.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/inject_header_style.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/osd_accessible_click.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.d.ts create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/register_listen_event_listener.js create mode 100644 plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/system_api.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a34c5c88..b09a7c01f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the Wazuh app project will be documented in this file. ### Added - Support for Wazuh 4.9.0 +- Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145) ## Wazuh v4.8.1 - OpenSearch Dashboards 2.10.0 - Revision 00 diff --git a/docker/osd-dev/dev.sh b/docker/osd-dev/dev.sh index 19558e9518..6e56b90aa0 100755 --- a/docker/osd-dev/dev.sh +++ b/docker/osd-dev/dev.sh @@ -12,6 +12,7 @@ os_versions=( '2.8.0' '2.9.0' '2.10.0' + '2.11.0' ) osd_versions=( @@ -26,6 +27,7 @@ osd_versions=( '2.8.0' '2.9.0' '2.10.0' + '2.11.0' '4.6.0' '4.7.0' ) diff --git a/plugins/main/package.json b/plugins/main/package.json index 81563935b9..0641b43211 100644 --- a/plugins/main/package.json +++ b/plugins/main/package.json @@ -3,7 +3,7 @@ "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.10.0" + "version": "2.11.0" }, "description": "Wazuh dashboard", "keywords": [ @@ -44,8 +44,10 @@ "prebuild": "node scripts/generate-build-version" }, "dependencies": { + "angular": "^1.8.2", "angular-animate": "1.8.3", "angular-material": "1.2.5", + "angular-sanitize": "^1.8.0", "axios": "^1.6.1", "install": "^0.13.0", "js2xmlparser": "^5.0.0", @@ -69,6 +71,10 @@ "@types/node-cron": "^2.0.3", "@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/parser": "^6.2.1", + "angular-aria": "^1.8.0", + "angular-route": "^1.8.0", + "angular-mocks": "^1.8.2", + "ngreact": "^0.5.1", "eslint": "^8.46.0", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-typescript": "3.5.5", diff --git a/plugins/main/public/components/common/modules/discover/discover.tsx b/plugins/main/public/components/common/modules/discover/discover.tsx index 130d33ec82..af57b12b9a 100644 --- a/plugins/main/public/components/common/modules/discover/discover.tsx +++ b/plugins/main/public/components/common/modules/discover/discover.tsx @@ -11,7 +11,10 @@ */ import React, { Component } from 'react'; import './discover.scss'; -import { FilterManager, Filter } from '../../../../../../../src/plugins/data/public/'; +import { + FilterManager, + Filter, +} from '../../../../../../../src/plugins/data/public/'; import { GenericRequest } from '../../../../react-services/generic-request'; import { AppState } from '../../../../react-services/app-state'; import { AppNavigate } from '../../../../react-services/app-navigate'; @@ -52,9 +55,13 @@ import { buildOpenSearchQuery, IFieldType, } from '../../../../../../../src/plugins/data/common'; -import { getDataPlugin, getToasts, getUiSettings } from '../../../../kibana-services'; +import { + getDataPlugin, + getToasts, + getUiSettings, +} from '../../../../kibana-services'; -const mapStateToProps = (state) => ({ +const mapStateToProps = state => ({ currentAgentData: state.appStateReducers.currentAgentData, }); @@ -66,7 +73,7 @@ interface ColumnDefinition { export const Discover = compose( withErrorBoundary, withReduxProvider, - connect(mapStateToProps) + connect(mapStateToProps), )( class Discover extends Component { _isMount!: boolean; @@ -156,12 +163,14 @@ export const Discover = compose( async componentDidMount() { this._isMount = true; try { - this.timeSubscription = this.timefilter.getTimeUpdate$().subscribe(() => { - this.setState({ - dateRange: this.timefilter.getTime(), - dateRangeHistory: this.timefilter._history, + this.timeSubscription = this.timefilter + .getTimeUpdate$() + .subscribe(() => { + this.setState({ + dateRange: this.timefilter.getTime(), + dateRangeHistory: this.timefilter._history, + }); }); - }); this.setState({ columns: this.getColumns() }); //initial columns await this.getIndexPattern(); await this.getAlerts(); @@ -208,7 +217,9 @@ export const Discover = compose( this.props.refreshAngularDiscover !== prevProps.refreshAngularDiscover ) { this.setState({ pageIndex: 0, tsUpdated: Date.now() }); - if (!_.isEqual(this.props.shareFilterManager, this.state.searchBarFilters)) { + if ( + !_.isEqual(this.props.shareFilterManager, this.state.searchBarFilters) + ) { this.setState({ columns: this.getColumns(), searchBarFilters: this.props.shareFilterManager || [], @@ -218,7 +229,7 @@ export const Discover = compose( } if ( ['pageIndex', 'pageSize', 'sortField', 'sortDirection'].some( - (field) => this.state[field] !== prevState[field] + field => this.state[field] !== prevState[field], ) || this.state.tsUpdated !== prevState.tsUpdated ) { @@ -249,10 +260,12 @@ export const Discover = compose( } getColumns() { //Extract array of terms from object - return this.getInnitialDefinitions().map((column) => column.field); + return this.getInnitialDefinitions().map(column => column.field); } getLabel(field) { - const innitialLabels = this.getInnitialDefinitions().filter((value) => value.field === field); + const innitialLabels = this.getInnitialDefinitions().filter( + value => value.field === field, + ); if (innitialLabels.length) { return innitialLabels[0].label || field; } else { @@ -262,18 +275,22 @@ export const Discover = compose( async getIndexPattern() { this.indexPattern = { - ...(await this.PluginPlatformServices.indexPatterns.get(AppState.getCurrentPattern())), + ...(await this.PluginPlatformServices.indexPatterns.get( + AppState.getCurrentPattern(), + )), }; } hideCreateCustomLabel = () => { try { const button = document.querySelector( - '.wz-discover #addFilterPopover > div > button > span > span' + '.wz-discover #addFilterPopover > div > button > span > span', ); if (!button) return setTimeout(this.hideCreateCustomLabel, 100); const findAndHide = () => { - const switcher = document.querySelector('#filterEditorCustomLabelSwitch'); + const switcher = document.querySelector( + '#filterEditorCustomLabelSwitch', + ); if (!switcher) return setTimeout(findAndHide, 100); switcher.parentElement.style.display = 'none'; }; @@ -304,7 +321,7 @@ export const Discover = compose( return result; } - toggleDetails = (item) => { + toggleDetails = item => { const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap }; const { rowDetailsFields } = this.props; @@ -318,9 +335,9 @@ export const Discover = compose( {' '} this.addFilter(filter)} - addFilterOut={(filter) => this.addFilterOut(filter)} - toggleColumn={(id) => this.addColumn(id)} + addFilter={filter => this.addFilter(filter)} + addFilterOut={filter => this.addFilterOut(filter)} + toggleColumn={id => this.addColumn(id)} rowDetailsFields={rowDetailsFields} indexPattern={this.indexPattern} /> @@ -331,8 +348,10 @@ export const Discover = compose( }; buildFilter() { - const dateParse = (ds) => - /\d+-\d+-\d+T\d+:\d+:\d+.\d+Z/.test(ds) ? DateMatch.parse(ds).toDate().getTime() : ds; + const dateParse = ds => + /\d+-\d+-\d+T\d+:\d+:\d+.\d+Z/.test(ds) + ? DateMatch.parse(ds).toDate().getTime() + : ds; const { query } = this.state; const { hideManagerAlerts } = this.wazuhConfig.getConfig(); const extraFilters = []; @@ -355,7 +374,9 @@ export const Discover = compose( ? this.props.shareFilterManager.getFilters() : []; const previousFilters = - (this.PluginPlatformServices && this.PluginPlatformServices.query.filterManager.getFilters()) || []; + (this.PluginPlatformServices && + this.PluginPlatformServices.query.filterManager.getFilters()) || + []; const elasticQuery = buildOpenSearchQuery( this.indexPattern, query, @@ -363,9 +384,9 @@ export const Discover = compose( previousFilters, filters, extraFilters, - this.props.shareFilterManagerWithUserAuthorized || [] + this.props.shareFilterManagerWithUserAuthorized || [], ), - getOpenSearchQueryConfig(getUiSettings()) + getOpenSearchQueryConfig(getUiSettings()), ); const { sortField, sortDirection } = this.state; @@ -382,10 +403,10 @@ export const Discover = compose( elasticQuery.bool.must.push(range); if (this.props.implicitFilters) { - this.props.implicitFilters.map((impicitFilter) => + this.props.implicitFilters.map(impicitFilter => elasticQuery.bool.must.push({ match: impicitFilter, - }) + }), ); } if (this.props.currentAgentData.id) { @@ -397,7 +418,9 @@ export const Discover = compose( query: elasticQuery, size: this.state.pageSize, from: this.state.pageIndex * this.state.pageSize, - ...(sortField ? { sort: { [sortField]: { order: sortDirection } } } : {}), + ...(sortField + ? { sort: { [sortField]: { order: sortDirection } } } + : {}), }; } @@ -435,8 +458,8 @@ export const Discover = compose( } const columns = this.state.columns; columns.splice( - columns.findIndex((v) => v === id), - 1 + columns.findIndex(v => v === id), + 1, ); this.setState(columns); } @@ -446,7 +469,7 @@ export const Discover = compose( this.showToast('warning', 'The maximum number of columns is 10', 3000); return; } - if (this.state.columns.find((element) => element === id)) { + if (this.state.columns.find(element => element === id)) { this.removeColumn(id); return; } @@ -457,16 +480,20 @@ export const Discover = compose( columns = () => { var columnsList = [...this.state.columns]; - const columns = columnsList.map((item) => { + const columns = columnsList.map(item => { if (item === 'icon') { return { width: '2.3%', isExpander: true, - render: (item) => { + render: item => { return ( ); }, @@ -478,7 +505,7 @@ export const Discover = compose( name: 'Time', width: '10%', sortable: true, - render: (time) => { + render: time => { return {formatUIDate(time)}; }, }; @@ -495,7 +522,10 @@ export const Discover = compose( if (item === 'agent.id') { link = (ev, x) => { - AppNavigate.navigateToModule(ev, 'agents', { tab: 'welcome', agent: x }); + AppNavigate.navigateToModule(ev, 'agents', { + tab: 'welcome', + agent: x, + }); }; width = '8%'; } @@ -507,10 +537,16 @@ export const Discover = compose( } if (item === 'rule.id') { link = (ev, x) => - AppNavigate.navigateToModule(ev, 'manager', { tab: 'rules', redirectRule: x }); + AppNavigate.navigateToModule(ev, 'manager', { + tab: 'rules', + redirectRule: x, + }); width = '9%'; } - if (item === 'rule.description' && columnsList.indexOf('syscheck.event') === -1) { + if ( + item === 'rule.description' && + columnsList.indexOf('syscheck.event') === -1 + ) { width = '30%'; } if (item === 'syscheck.event') { @@ -537,16 +573,20 @@ export const Discover = compose( > {this.getLabel(item)}{' '} {this.state.hover === item && ( - + { + style={{ + paddingBottom: 12, + marginBottom: '-10px', + paddingTop: 0, + }} + onClick={e => { this.removeColumn(item); e.stopPropagation(); }} - iconType="cross" - aria-label="Filter" - iconSize="s" + iconType='cross' + aria-label='Filter' + iconSize='s' /> )} @@ -562,20 +602,23 @@ export const Discover = compose( (link && item !== 'rule.mitre.id') || (item === 'rule.mitre.id' && this.props.shareFilterManager) ) { - column.render = (itemValue) => { + column.render = itemValue => { return ( {(item === 'agent.id' && itemValue === '000' && ( - {itemValue} + + {itemValue} + )) || (item === 'rule.mitre.id' && Array.isArray(itemValue) && - itemValue.map((currentItem) => ( + itemValue.map((currentItem, key) => ( { + key={key} + onClick={ev => { ev.stopPropagation(); }} - onMouseDown={(ev) => { + onMouseDown={ev => { ev.stopPropagation(); link(ev, currentItem); }} @@ -584,10 +627,10 @@ export const Discover = compose( ))) || ( { + onClick={ev => { ev.stopPropagation(); }} - onMouseDown={(ev) => { + onMouseDown={ev => { ev.stopPropagation(); link(ev, itemValue); }} @@ -634,11 +677,11 @@ export const Discover = compose( const key = Object.keys(filter)[0]; const value = filter[key]; const valuesArray = Array.isArray(value) ? [...value] : [value]; - valuesArray.map((item) => { + valuesArray.map(item => { const formattedFilter = buildPhraseFilter( { name: key, type: 'string' }, item, - this.indexPattern + this.indexPattern, ); formattedFilter.meta.negate = true; @@ -656,11 +699,11 @@ export const Discover = compose( const key = Object.keys(filter)[0]; const value = filter[key]; const valuesArray = Array.isArray(value) ? [...value] : [value]; - valuesArray.map((item) => { + valuesArray.map(item => { const formattedFilter = buildPhraseFilter( { name: key, type: 'string' }, item, - this.indexPattern + this.indexPattern, ); if ( formattedFilter.meta.key === 'manager.name' || @@ -673,7 +716,10 @@ export const Discover = compose( this.setState({ pageIndex: 0, tsUpdated: Date.now() }); } - onQuerySubmit = (payload: { dateRange: TimeRange; query: Query | undefined }) => { + onQuerySubmit = (payload: { + dateRange: TimeRange; + query: Query | undefined; + }) => { this.setState({ ...payload, tsUpdated: Date.now() }); }; @@ -681,7 +727,6 @@ export const Discover = compose( this.setState({ pageIndex: 0, tsUpdated: Date.now() }); }; - openDiscover(e, techniqueID) { AppNavigate.navigateToModule(e, 'overview', { tab: 'mitre', @@ -699,7 +744,12 @@ export const Discover = compose( } openIntelligence(e, redirectTo, itemID) { - AppNavigate.navigateToModule(e, 'overview', { "tab": 'mitre', "tabView": "intelligence", "tabRedirect": redirectTo, "idToRedirect": itemID }); + AppNavigate.navigateToModule(e, 'overview', { + tab: 'mitre', + tabView: 'intelligence', + tabRedirect: redirectTo, + idToRedirect: itemID, + }); } render() { @@ -711,7 +761,7 @@ export const Discover = compose( ); const { total, itemIdToExpandedRowMap } = this.state; const { query = this.state.query } = this.props; - const getRowProps = (item) => { + const getRowProps = item => { const { _id } = item; return { 'data-test-subj': `row-${_id}`, @@ -737,7 +787,7 @@ export const Discover = compose( }; const noResultsText = `No results match for this search criteria`; return ( -
+
{this.props.kbnSearchBar && ( this.setState({ dateRange }), + setTimeFilter: dateRange => this.setState({ dateRange }), }} onQuerySubmit={this.onQuerySubmit} onFiltersUpdated={this.onFiltersUpdated} @@ -757,9 +807,12 @@ export const Discover = compose( {this.state.alerts.length && ( ({ ...alert._source, _id: alert._id }))} - className="module-discover-table" - itemId="_id" + items={this.state.alerts.map(alert => ({ + ...alert._source, + _id: alert._id, + }))} + className='module-discover-table' + itemId='_id' itemIdToExpandedRowMap={itemIdToExpandedRowMap} isExpandable={true} columns={columns} @@ -774,13 +827,17 @@ export const Discover = compose( ) : ( - - + + )}
); } - } + }, ); diff --git a/plugins/main/public/get_inner_angular.ts b/plugins/main/public/get_inner_angular.ts index c186f65f1d..637f2a1b32 100644 --- a/plugins/main/public/get_inner_angular.ts +++ b/plugins/main/public/get_inner_angular.ts @@ -23,8 +23,16 @@ import angular from 'angular'; // required for `ngSanitize` angular module import 'angular-sanitize'; -import { i18nDirective, i18nFilter, I18nProvider } from '@osd/i18n/angular'; -import { CoreStart, PluginInitializerContext } from 'opensearch_dashboards/public'; +import 'ngreact'; +import { + i18nDirective, + i18nFilter, + I18nProvider, +} from './kibana-integrations/packages/osd-i18n/angular'; +import { + CoreStart, + PluginInitializerContext, +} from 'opensearch_dashboards/public'; import { Storage } from '../../../src/plugins/opensearch_dashboards_utils/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../src/plugins/navigation/public'; import { @@ -36,7 +44,7 @@ import { watchMultiDecorator, createTopNavDirective, createTopNavHelper, -} from '../../../src/plugins/opensearch_dashboards_legacy/public'; +} from './kibana-integrations/plugins/opensearch_dashboards_legacy/public'; import { AppPluginStartDependencies } from './types'; import { getScopedHistory, setDiscoverModule } from './kibana-services'; import { createDiscoverLegacyDirective } from './kibana-integrations/discover/application/components/create_discover_legacy_directive'; @@ -51,17 +59,25 @@ export function getInnerAngularModule( name: string, core: CoreStart, deps: AppPluginStartDependencies, - context: PluginInitializerContext + context: PluginInitializerContext, ) { initAngularBootstrap(); const module = initializeInnerAngularModule(name, deps.navigation); - configureAppAngularModule(module, { core, env: context.env }, true, getScopedHistory); + configureAppAngularModule( + module, + { core, env: context.env }, + true, + getScopedHistory, + ); return module; } let initialized = false; -export function initializeInnerAngularModule(name = 'app/wazuh', navigation: NavigationStart) { +export function initializeInnerAngularModule( + name = 'app/wazuh', + navigation: NavigationStart, +) { if (!initialized) { createLocalI18nModule(); createLocalPrivateModule(); @@ -93,12 +109,14 @@ export function initializeInnerAngularModule(name = 'app/wazuh', navigation: Nav .config(watchMultiDecorator) .run(registerListenEventListener) .directive('discoverLegacy', createDiscoverLegacyDirective) - .directive('icon', (reactDirective) => reactDirective(EuiIcon)) + .directive('icon', reactDirective => reactDirective(EuiIcon)) .directive('contextErrorMessage', createContextErrorMessageDirective); } function createLocalPromiseModule() { - angular.module('discoverPromise', []).service('Promise', PromiseServiceCreator); + angular + .module('discoverPromise', []) + .service('Promise', PromiseServiceCreator); } function createLocalPrivateModule() { diff --git a/plugins/main/public/kibana-integrations/kibana-discover.js b/plugins/main/public/kibana-integrations/kibana-discover.js index b0c5404fff..e571f705d3 100644 --- a/plugins/main/public/kibana-integrations/kibana-discover.js +++ b/plugins/main/public/kibana-integrations/kibana-discover.js @@ -32,12 +32,10 @@ import { getServices, setServices, setDocViewsRegistry, - subscribeWithScope, tabifyAggResponse, - getHeaderActionMenuMounter, setUiActions, } from './discover/kibana_services'; - +import { subscribeWithScope } from './plugins/opensearch_dashboards_legacy/public'; import indexTemplateLegacy from './discover/application/angular/discover_legacy.html'; getAngularModule().directive('kbnDis', [ diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/directive.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/directive.ts new file mode 100644 index 0000000000..790357da7d --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/directive.ts @@ -0,0 +1,126 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IDirective, IRootElementService, IScope } from 'angular'; + +import { I18nServiceType } from './provider'; + +interface I18nScope extends IScope { + values?: Record; + defaultMessage: string; + id: string; +} + +const HTML_KEY_PREFIX = 'html_'; +const PLACEHOLDER_SEPARATOR = '@I18N@'; + +export const i18nDirective: [string, string, typeof i18nDirectiveFn] = [ + 'i18n', + '$sanitize', + i18nDirectiveFn, +]; + +function i18nDirectiveFn( + i18n: I18nServiceType, + $sanitize: (html: string) => string +): IDirective { + return { + restrict: 'A', + scope: { + id: '@i18nId', + defaultMessage: '@i18nDefaultMessage', + values: ' { + setContent($element, $scope, $sanitize, i18n); + }); + } else { + setContent($element, $scope, $sanitize, i18n); + } + }, + }; +} + +function setContent( + $element: IRootElementService, + $scope: I18nScope, + $sanitize: (html: string) => string, + i18n: I18nServiceType +) { + const originalValues = $scope.values; + const valuesWithPlaceholders = {} as Record; + let hasValuesWithPlaceholders = false; + + // If we have values with the keys that start with HTML_KEY_PREFIX we should replace + // them with special placeholders that later on will be inserted as HTML + // into the DOM, the rest of the content will be treated as text. We don't + // sanitize values at this stage as some of the values can be excluded from + // the translated string (e.g. not used by ICU conditional statements). + if (originalValues) { + for (const [key, value] of Object.entries(originalValues)) { + if (key.startsWith(HTML_KEY_PREFIX)) { + valuesWithPlaceholders[ + key.slice(HTML_KEY_PREFIX.length) + ] = `${PLACEHOLDER_SEPARATOR}${key}${PLACEHOLDER_SEPARATOR}`; + + hasValuesWithPlaceholders = true; + } else { + valuesWithPlaceholders[key] = value; + } + } + } + + const label = i18n($scope.id, { + values: valuesWithPlaceholders, + defaultMessage: $scope.defaultMessage, + }); + + // If there are no placeholders to replace treat everything as text, otherwise + // insert label piece by piece replacing every placeholder with corresponding + // sanitized HTML content. + if (!hasValuesWithPlaceholders) { + $element.text(label); + } else { + $element.empty(); + for (const contentOrPlaceholder of label.split(PLACEHOLDER_SEPARATOR)) { + if (!contentOrPlaceholder) { + continue; + } + + $element.append( + originalValues!.hasOwnProperty(contentOrPlaceholder) + ? $sanitize(originalValues![contentOrPlaceholder]) + : document.createTextNode(contentOrPlaceholder) + ); + } + } +} diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/filter.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/filter.ts new file mode 100644 index 0000000000..4ffa5dd3ef --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/filter.ts @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { I18nServiceType } from './provider'; + +export const i18nFilter: [string, typeof i18nFilterFn] = ['i18n', i18nFilterFn]; + +function i18nFilterFn(i18n: I18nServiceType) { + return (id: string, { defaultMessage = '', values = {} } = {}) => { + return i18n(id, { + values, + defaultMessage, + }); + }; +} diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/index.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/index.ts new file mode 100644 index 0000000000..04f7d66eb1 --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/index.ts @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { I18nProvider } from './provider'; + +export { i18nFilter } from './filter'; +export { i18nDirective } from './directive'; + +// re-export types: https://github.com/babel/babel-loader/issues/603 +import { I18nServiceType as _I18nServiceType } from './provider'; +export type I18nServiceType = _I18nServiceType; diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/provider.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/provider.ts new file mode 100644 index 0000000000..bd02a30cef --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/angular/provider.ts @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as i18n from '../core'; + +export type I18nServiceType = ReturnType; + +export class I18nProvider implements angular.IServiceProvider { + public addTranslation = i18n.addTranslation; + public getTranslation = i18n.getTranslation; + public setLocale = i18n.setLocale; + public getLocale = i18n.getLocale; + public setDefaultLocale = i18n.setDefaultLocale; + public getDefaultLocale = i18n.getDefaultLocale; + public setFormats = i18n.setFormats; + public getFormats = i18n.getFormats; + public getRegisteredLocales = i18n.getRegisteredLocales; + public init = i18n.init; + public load = i18n.load; + public $get = () => i18n.translate; +} diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/core/formats.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/formats.ts new file mode 100644 index 0000000000..f87fd57e6c --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/formats.ts @@ -0,0 +1,163 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Default format options used for "en" locale. + * These are used when constructing the internal Intl.NumberFormat + * (`number` formatter) and Intl.DateTimeFormat (`date` and `time` formatters) instances. + * The value of each parameter of `number` formatter is options object which is + * described in `options` section of [NumberFormat constructor]. + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat} + * The value of each parameter of `date` and `time` formatters is options object which is + * described in `options` section of [DateTimeFormat constructor]. + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat} + */ +export const formats: Formats = { + number: { + currency: { + style: 'currency', + }, + percent: { + style: 'percent', + }, + }, + date: { + short: { + month: 'numeric', + day: 'numeric', + year: '2-digit', + }, + medium: { + month: 'short', + day: 'numeric', + year: 'numeric', + }, + long: { + month: 'long', + day: 'numeric', + year: 'numeric', + }, + full: { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric', + }, + }, + time: { + short: { + hour: 'numeric', + minute: 'numeric', + }, + medium: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }, + long: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short', + }, + full: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short', + }, + }, + relative: { + years: { + units: 'year', + }, + months: { + units: 'month', + }, + days: { + units: 'day', + }, + hours: { + units: 'hour', + }, + minutes: { + units: 'minute', + }, + seconds: { + units: 'second', + }, + }, +}; + +interface NumberFormatOptions extends Intl.NumberFormatOptions { + style?: TStyle; + localeMatcher?: 'lookup' | 'best fit'; + currencyDisplay?: 'symbol' | 'code' | 'name'; +} + +export interface Formats { + number?: Partial<{ + [key: string]: NumberFormatOptions<'currency' | 'percent' | 'decimal'>; + currency: NumberFormatOptions<'currency'>; + percent: NumberFormatOptions<'percent'>; + }>; + date?: Partial<{ + [key: string]: DateTimeFormatOptions; + short: DateTimeFormatOptions; + medium: DateTimeFormatOptions; + long: DateTimeFormatOptions; + full: DateTimeFormatOptions; + }>; + time?: Partial<{ + [key: string]: DateTimeFormatOptions; + short: DateTimeFormatOptions; + medium: DateTimeFormatOptions; + long: DateTimeFormatOptions; + full: DateTimeFormatOptions; + }>; + relative?: Partial<{ + [key: string]: { + style?: 'numeric' | 'best fit'; + units: 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'; + }; + }>; +} + +interface DateTimeFormatOptions extends Intl.DateTimeFormatOptions { + weekday?: 'narrow' | 'short' | 'long'; + era?: 'narrow' | 'short' | 'long'; + year?: 'numeric' | '2-digit'; + month?: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long'; + day?: 'numeric' | '2-digit'; + hour?: 'numeric' | '2-digit'; + minute?: 'numeric' | '2-digit'; + second?: 'numeric' | '2-digit'; + timeZoneName?: 'short' | 'long'; +} diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/core/helper.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/helper.ts new file mode 100644 index 0000000000..528465e539 --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/helper.ts @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const isString = (value: any): value is string => typeof value === 'string'; + +export const isObject = (value: any): value is object => + typeof value === 'object' && value !== null; + +export const hasValues = (values: any) => Object.keys(values).length > 0; + +export const unique = (arr: T[] = []): T[] => [...new Set(arr)]; + +const merge = (a: any, b: any): { [k: string]: any } => + unique([...Object.keys(a), ...Object.keys(b)]).reduce((acc, key) => { + if (isObject(a[key]) && isObject(b[key]) && !Array.isArray(a[key]) && !Array.isArray(b[key])) { + return { + ...acc, + [key]: merge(a[key], b[key]), + }; + } + + return { + ...acc, + [key]: b[key] === undefined ? a[key] : b[key], + }; + }, {}); + +export const mergeAll = (...sources: any[]) => + sources.filter(isObject).reduce((acc, source) => merge(acc, source)); diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/core/i18n.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/i18n.ts new file mode 100644 index 0000000000..3268fae507 --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/i18n.ts @@ -0,0 +1,265 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import memoizeIntlConstructor from 'intl-format-cache'; +import IntlMessageFormat from 'intl-messageformat'; +import IntlRelativeFormat from 'intl-relativeformat'; + +import { Translation } from '../translation'; +import { Formats, formats as EN_FORMATS } from './formats'; +import { hasValues, isObject, isString, mergeAll } from './helper'; +import { isPseudoLocale, translateUsingPseudoLocale } from './pseudo_locale'; + +// Add all locale data to `IntlMessageFormat`. +import './locales.js'; + +const EN_LOCALE = 'en'; +const translationsForLocale: Record = {}; +const getMessageFormat = memoizeIntlConstructor(IntlMessageFormat); + +let defaultLocale = EN_LOCALE; +let currentLocale = EN_LOCALE; +let formats = EN_FORMATS; + +IntlMessageFormat.defaultLocale = defaultLocale; +IntlRelativeFormat.defaultLocale = defaultLocale; + +/** + * Returns message by the given message id. + * @param id - path to the message + */ +function getMessageById(id: string): string | undefined { + const translation = getTranslation(); + return translation.messages ? translation.messages[id] : undefined; +} + +/** + * Normalizes locale to make it consistent with IntlMessageFormat locales + * @param locale + */ +function normalizeLocale(locale: string) { + return locale.toLowerCase(); +} + +/** + * Provides a way to register translations with the engine + * @param newTranslation + * @param [locale = messages.locale] + */ +export function addTranslation(newTranslation: Translation, locale = newTranslation.locale) { + if (!locale || !isString(locale)) { + throw new Error('[I18n] A `locale` must be a non-empty string to add messages.'); + } + + if (newTranslation.locale && newTranslation.locale !== locale) { + throw new Error( + '[I18n] A `locale` in the translation object is different from the one provided as a second argument.' + ); + } + + const normalizedLocale = normalizeLocale(locale); + const existingTranslation = translationsForLocale[normalizedLocale] || { messages: {} }; + + translationsForLocale[normalizedLocale] = { + formats: newTranslation.formats || existingTranslation.formats, + locale: newTranslation.locale || existingTranslation.locale, + messages: { + ...existingTranslation.messages, + ...newTranslation.messages, + }, + }; +} + +/** + * Returns messages for the current language + */ +export function getTranslation(): Translation { + return translationsForLocale[currentLocale] || { messages: {} }; +} + +/** + * Tells the engine which language to use by given language key + * @param locale + */ +export function setLocale(locale: string) { + if (!locale || !isString(locale)) { + throw new Error('[I18n] A `locale` must be a non-empty string.'); + } + + currentLocale = normalizeLocale(locale); +} + +/** + * Returns the current locale + */ +export function getLocale() { + return currentLocale; +} + +/** + * Tells the library which language to fallback when missing translations + * @param locale + */ +export function setDefaultLocale(locale: string) { + if (!locale || !isString(locale)) { + throw new Error('[I18n] A `locale` must be a non-empty string.'); + } + + defaultLocale = normalizeLocale(locale); + IntlMessageFormat.defaultLocale = defaultLocale; + IntlRelativeFormat.defaultLocale = defaultLocale; +} + +export function getDefaultLocale() { + return defaultLocale; +} + +/** + * Supplies a set of options to the underlying formatter + * [Default format options used as the prototype of the formats] + * {@link https://github.com/yahoo/intl-messageformat/blob/master/src/core.js#L62} + * These are used when constructing the internal Intl.NumberFormat + * and Intl.DateTimeFormat instances. + * @param newFormats + * @param [newFormats.number] + * @param [newFormats.date] + * @param [newFormats.time] + */ +export function setFormats(newFormats: Formats) { + if (!isObject(newFormats) || !hasValues(newFormats)) { + throw new Error('[I18n] A `formats` must be a non-empty object.'); + } + + formats = mergeAll(formats, newFormats); +} + +/** + * Returns current formats + */ +export function getFormats() { + return formats; +} + +/** + * Returns array of locales having translations + */ +export function getRegisteredLocales() { + return Object.keys(translationsForLocale); +} + +interface TranslateArguments { + values?: Record; + defaultMessage: string; + description?: string; +} + +/** + * Translate message by id + * @param id - translation id to be translated + * @param [options] + * @param [options.values] - values to pass into translation + * @param [options.defaultMessage] - will be used unless translation was successful + */ +export function translate(id: string, { values = {}, defaultMessage }: TranslateArguments) { + const shouldUsePseudoLocale = isPseudoLocale(currentLocale); + + if (!id || !isString(id)) { + throw new Error('[I18n] An `id` must be a non-empty string to translate a message.'); + } + + const message = shouldUsePseudoLocale ? defaultMessage : getMessageById(id); + + if (!message && !defaultMessage) { + throw new Error(`[I18n] Cannot format message: "${id}". Default message must be provided.`); + } + + if (message) { + try { + // We should call `format` even for messages without any value references + // to let it handle escaped curly braces `\\{` that are the part of the text itself + // and not value reference boundaries. + const formattedMessage = getMessageFormat(message, getLocale(), getFormats()).format(values); + + return shouldUsePseudoLocale + ? translateUsingPseudoLocale(formattedMessage) + : formattedMessage; + } catch (e) { + throw new Error( + `[I18n] Error formatting message: "${id}" for locale: "${getLocale()}".\n${e}` + ); + } + } + + try { + const msg = getMessageFormat(defaultMessage, getDefaultLocale(), getFormats()); + + return msg.format(values); + } catch (e) { + throw new Error(`[I18n] Error formatting the default message for: "${id}".\n${e}`); + } +} + +/** + * Initializes the engine + * @param newTranslation + */ +export function init(newTranslation?: Translation) { + if (!newTranslation) { + return; + } + + addTranslation(newTranslation); + + if (newTranslation.locale) { + setLocale(newTranslation.locale); + } + + if (newTranslation.formats) { + setFormats(newTranslation.formats); + } +} + +/** + * Loads JSON with translations from the specified URL and initializes i18n engine with them. + * @param translationsUrl URL pointing to the JSON bundle with translations. + */ +export async function load(translationsUrl: string) { + // Once this package is integrated into core OpenSearch Dashboards we should switch to an abstraction + // around `fetch` provided by the platform, e.g. `kfetch`. + const response = await fetch(translationsUrl, { + credentials: 'same-origin', + }); + + if (response.status >= 300) { + throw new Error(`Translations request failed with status code: ${response.status}`); + } + + init(await response.json()); +} diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/core/index.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/index.ts new file mode 100644 index 0000000000..fe0619fa1e --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/index.ts @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { formats } from './formats'; +export * from './i18n'; diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/core/locales.js b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/locales.js new file mode 100644 index 0000000000..a837462b74 --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/locales.js @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* eslint-disable */ + +// Copied from https://github.com/yahoo/intl-relativeformat/tree/master/dist/locale-data + +import IntlMessageFormat from 'intl-messageformat'; +import IntlRelativeFormat from 'intl-relativeformat'; + +function addLocaleData(localeData) { + IntlMessageFormat.__addLocaleData(localeData); + IntlRelativeFormat.__addLocaleData(localeData); +} + +addLocaleData({ locale: "en", pluralRuleFunction: function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"},"fields":{"year":{"displayName":"year","relative":{"0":"this year","1":"next year","-1":"last year"},"relativeTime":{"future":{"one":"in {0} year","other":"in {0} years"},"past":{"one":"{0} year ago","other":"{0} years ago"}}},"year-short":{"displayName":"yr.","relative":{"0":"this yr.","1":"next yr.","-1":"last yr."},"relativeTime":{"future":{"one":"in {0} yr.","other":"in {0} yr."},"past":{"one":"{0} yr. ago","other":"{0} yr. ago"}}},"month":{"displayName":"month","relative":{"0":"this month","1":"next month","-1":"last month"},"relativeTime":{"future":{"one":"in {0} month","other":"in {0} months"},"past":{"one":"{0} month ago","other":"{0} months ago"}}},"month-short":{"displayName":"mo.","relative":{"0":"this mo.","1":"next mo.","-1":"last mo."},"relativeTime":{"future":{"one":"in {0} mo.","other":"in {0} mo."},"past":{"one":"{0} mo. ago","other":"{0} mo. ago"}}},"day":{"displayName":"day","relative":{"0":"today","1":"tomorrow","-1":"yesterday"},"relativeTime":{"future":{"one":"in {0} day","other":"in {0} days"},"past":{"one":"{0} day ago","other":"{0} days ago"}}},"day-short":{"displayName":"day","relative":{"0":"today","1":"tomorrow","-1":"yesterday"},"relativeTime":{"future":{"one":"in {0} day","other":"in {0} days"},"past":{"one":"{0} day ago","other":"{0} days ago"}}},"hour":{"displayName":"hour","relative":{"0":"this hour"},"relativeTime":{"future":{"one":"in {0} hour","other":"in {0} hours"},"past":{"one":"{0} hour ago","other":"{0} hours ago"}}},"hour-short":{"displayName":"hr.","relative":{"0":"this hour"},"relativeTime":{"future":{"one":"in {0} hr.","other":"in {0} hr."},"past":{"one":"{0} hr. ago","other":"{0} hr. ago"}}},"minute":{"displayName":"minute","relative":{"0":"this minute"},"relativeTime":{"future":{"one":"in {0} minute","other":"in {0} minutes"},"past":{"one":"{0} minute ago","other":"{0} minutes ago"}}},"minute-short":{"displayName":"min.","relative":{"0":"this minute"},"relativeTime":{"future":{"one":"in {0} min.","other":"in {0} min."},"past":{"one":"{0} min. ago","other":"{0} min. ago"}}},"second":{"displayName":"second","relative":{"0":"now"},"relativeTime":{"future":{"one":"in {0} second","other":"in {0} seconds"},"past":{"one":"{0} second ago","other":"{0} seconds ago"}}},"second-short":{"displayName":"sec.","relative":{"0":"now"},"relativeTime":{"future":{"one":"in {0} sec.","other":"in {0} sec."},"past":{"one":"{0} sec. ago","other":"{0} sec. ago"}}}} }); +addLocaleData({ locale: "en-US", parentLocale: "en" }); +addLocaleData({ locale: "en-xa", pluralRuleFunction: function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}, "fields":{"year":{"displayName":"Ć½Ć©Ć Ć Å•","relative":{"0":"Å£Ä„Ć®Ć®Å” Ć½Ć©Ć©Ć Å•","1":"ƱƩįŗ‹įŗ‹Å£ Ć½Ć©Ć©Ć Å•","-1":"Ä¼Ć Å”Å”Å£ Ć½Ć©Ć©Ć Å•"},"relativeTime":{"future":{"one":"ƮƱ {0} Ć½Ć½Ć©Ć Å•Å•","other":"ƮƱ {0} Ć½Ć½Ć©Ć Å•Å•Å”"},"past":{"one":"{0} Ć½Ć©Ć Ć Å• Ć ÄÄĆ“","other":"{0} Ć½Ć©Ć Ć Å•Å” Ć Ć ÄĆ“"}}},"year-short":{"displayName":"Ć½Å•.","relative":{"0":"Å£Ä„Ć®Ć®Å” Ć½Å•Å•.","1":"ƱƩįŗ‹įŗ‹Å£ Ć½Å•Å•.","-1":"Ä¼Ć Å”Å”Å£ Ć½Å•Å•."},"relativeTime":{"future":{"one":"ƮƱ {0} Ć½Ć½Å•.","other":"ƮƱ {0} Ć½Ć½Å•."},"past":{"one":"{0} Ć½Å•. Ć Ć ÄĆ“","other":"{0} Ć½Å•. Ć Ć ÄĆ“"}}},"month":{"displayName":"É±Ć“Ć±Ć±Å£Ä„","relative":{"0":"Å£Ä„Ć®Ć®Å” É±Ć“Ć“Ć±Å£Ä„Ä„","1":"ƱƩįŗ‹įŗ‹Å£ É±Ć“Ć“Ć±Å£Ä„Ä„","-1":"Ä¼Ć Å”Å”Å£ É±Ć“Ć“Ć±Å£Ä„Ä„"},"relativeTime":{"future":{"one":"ƮƱ {0} É±É±Ć“Ć±Å£Å£Ä„","other":"ƮƱ {0} É±É±Ć“Ć±Å£Å£Ä„Å”"},"past":{"one":"{0} É±Ć“Ć±Ć±Å£Ä„ Ć Ć ÄĆ“","other":"{0} É±Ć“Ć±Ć±Å£Ä„Å”Å” Ć ÄĆ“Ć“"}}},"month-short":{"displayName":"ɱƓ.","relative":{"0":"Å£Ä„Ć®Ć®Å” ɱƓƓ.","1":"ƱƩįŗ‹įŗ‹Å£ ɱƓƓ.","-1":"Ä¼Ć Å”Å”Å£ ɱƓƓ."},"relativeTime":{"future":{"one":"ƮƱ {0} ɱɱƓ.","other":"ƮƱ {0} ɱɱƓ."},"past":{"one":"{0} ɱƓ. Ć Ć ÄĆ“","other":"{0} ɱƓ. Ć Ć ÄĆ“"}}},"day":{"displayName":"Ć°Ć Ć½Ć½","relative":{"0":"Å£Ć“Ć°Ć°Ć Ć½","1":"Å£Ć“É±É±Ć“Å•Å•Å•Ć“Åµ","-1":"Ć½Ć©Å”Å”Å£Ć©Å•Å•Ć°Ć Ć½Ć½"},"relativeTime":{"future":{"one":"ƮƱ {0} Ć°Ć°Ć Ć½","other":"ƮƱ {0} Ć°Ć°Ć Ć½Å”Å”"},"past":{"one":"{0} Ć°Ć Ć½Ć½ Ć ÄĆ“Ć“","other":"{0} Ć°Ć Ć½Ć½Å” Ć ÄÄĆ“"}}},"day-short":{"displayName":"Ć°Ć Ć½Ć½","relative":{"0":"Å£Ć“Ć°Ć°Ć Ć½","1":"Å£Ć“É±É±Ć“Å•Å•Å•Ć“Åµ","-1":"Ć½Ć©Å”Å”Å£Ć©Å•Å•Ć°Ć Ć½Ć½"},"relativeTime":{"future":{"one":"ƮƱ {0} Ć°Ć°Ć Ć½","other":"ƮƱ {0} Ć°Ć°Ć Ć½Å”Å”"},"past":{"one":"{0} Ć°Ć Ć½Ć½ Ć ÄĆ“Ć“","other":"{0} Ć°Ć Ć½Ć½Å” Ć ÄÄĆ“"}}},"hour":{"displayName":"Ä„Ć“Ć»Ć»Å•","relative":{"0":"Å£Ä„Ć®Ć®Å” Ä„Ć“Ć“Ć»Å•"},"relativeTime":{"future":{"one":"ƮƱ {0} Ä„Ä„Ć“Ć»Å•Å•","other":"ƮƱ {0} Ä„Ä„Ć“Ć»Å•Å•Å”"},"past":{"one":"{0} Ä„Ć“Ć»Ć»Å• Ć ÄÄĆ“","other":"{0} Ä„Ć“Ć»Ć»Å•Å” Ć Ć ÄĆ“"}}},"hour-short":{"displayName":"Ä„Å•.","relative":{"0":"Å£Ä„Ć®Ć®Å” Ä„Ć“Ć“Ć»Å•"},"relativeTime":{"future":{"one":"ƮƱ {0} Ä„Ä„Å•.","other":"ƮƱ {0} Ä„Ä„Å•."},"past":{"one":"{0} Ä„Å•. Ć Ć ÄĆ“","other":"{0} Ä„Å•. Ć Ć ÄĆ“"}}},"minute":{"displayName":"É±Ć®Ć±Ć±Ć»Å£Ć©Ć©","relative":{"0":"Å£Ä„Ć®Ć®Å” É±Ć®Ć®Ć±Ć»Å£Å£Ć©"},"relativeTime":{"future":{"one":"ƮƱ {0} É±É±Ć®Ć±Ć»Ć»Å£Ć©","other":"ƮƱ {0} É±É±Ć®Ć±Ć»Ć»Å£Ć©Å”Å”"},"past":{"one":"{0} É±Ć®Ć±Ć±Ć»Å£Ć©Ć© Ć ÄĆ“Ć“","other":"{0} É±Ć®Ć±Ć±Ć»Å£Ć©Ć©Å” Ć ÄÄĆ“"}}},"minute-short":{"displayName":"ɱƮƱƱ.","relative":{"0":"Å£Ä„Ć®Ć®Å” É±Ć®Ć®Ć±Ć»Å£Å£Ć©"},"relativeTime":{"future":{"one":"ƮƱ {0} ɱɱƮƱ.","other":"ƮƱ {0} ɱɱƮƱ."},"past":{"one":"{0} ɱƮƱƱ. Ć ÄĆ“Ć“","other":"{0} ɱƮƱƱ. Ć ÄĆ“Ć“"}}},"second":{"displayName":"Å”Ć©Ć§Ć§Ć“Ć±Ć°Ć°","relative":{"0":"Ć±Ć“ÅµÅµ"},"relativeTime":{"future":{"one":"ƮƱ {0} Å”Å”Ć©Ć§Ć“Ć“Ć±Ć°","other":"ƮƱ {0} Å”Å”Ć©Ć§Ć“Ć“Ć±Ć°Å”Å”"},"past":{"one":"{0} Å”Ć©Ć§Ć§Ć“Ć±Ć°Ć° Ć ÄĆ“Ć“","other":"{0} Å”Ć©Ć§Ć§Ć“Ć±Ć°Ć°Å” Ć ÄÄĆ“"}}},"second-short":{"displayName":"Å”Ć©Ć§Ć§.","relative":{"0":"Ć±Ć“ÅµÅµ"},"relativeTime":{"future":{"one":"ƮƱ {0} Å”Å”Ć©Ć§.","other":"ƮƱ {0} Å”Å”Ć©Ć§."},"past":{"one":"{0} Å”Ć©Ć§Ć§. Ć ÄĆ“Ć“","other":"{0} Å”Ć©Ć§Ć§. Ć ÄĆ“Ć“"}}}} }); +addLocaleData({ locale: "es", pluralRuleFunction: function (n,ord){if(ord)return"other";return n==1?"one":"other"},"fields":{"year":{"displayName":"aƱo","relative":{"0":"este aƱo","1":"el prĆ³ximo aƱo","-1":"el aƱo pasado"},"relativeTime":{"future":{"one":"dentro de {0} aƱo","other":"dentro de {0} aƱos"},"past":{"one":"hace {0} aƱo","other":"hace {0} aƱos"}}},"year-short":{"displayName":"a","relative":{"0":"este aƱo","1":"el prĆ³ximo aƱo","-1":"el aƱo pasado"},"relativeTime":{"future":{"one":"dentro de {0} a","other":"dentro de {0} a"},"past":{"one":"hace {0} a","other":"hace {0} a"}}},"month":{"displayName":"mes","relative":{"0":"este mes","1":"el prĆ³ximo mes","-1":"el mes pasado"},"relativeTime":{"future":{"one":"dentro de {0} mes","other":"dentro de {0} meses"},"past":{"one":"hace {0} mes","other":"hace {0} meses"}}},"month-short":{"displayName":"m","relative":{"0":"este mes","1":"el prĆ³ximo mes","-1":"el mes pasado"},"relativeTime":{"future":{"one":"dentro de {0} m","other":"dentro de {0} m"},"past":{"one":"hace {0} m","other":"hace {0} m"}}},"day":{"displayName":"dĆ­a","relative":{"0":"hoy","1":"maƱana","2":"pasado maƱana","-2":"anteayer","-1":"ayer"},"relativeTime":{"future":{"one":"dentro de {0} dĆ­a","other":"dentro de {0} dĆ­as"},"past":{"one":"hace {0} dĆ­a","other":"hace {0} dĆ­as"}}},"day-short":{"displayName":"d","relative":{"0":"hoy","1":"maƱana","2":"pasado maƱana","-2":"anteayer","-1":"ayer"},"relativeTime":{"future":{"one":"dentro de {0} dĆ­a","other":"dentro de {0} dĆ­as"},"past":{"one":"hace {0} dĆ­a","other":"hace {0} dĆ­as"}}},"hour":{"displayName":"hora","relative":{"0":"esta hora"},"relativeTime":{"future":{"one":"dentro de {0} hora","other":"dentro de {0} horas"},"past":{"one":"hace {0} hora","other":"hace {0} horas"}}},"hour-short":{"displayName":"h","relative":{"0":"esta hora"},"relativeTime":{"future":{"one":"dentro de {0} h","other":"dentro de {0} h"},"past":{"one":"hace {0} h","other":"hace {0} h"}}},"minute":{"displayName":"minuto","relative":{"0":"este minuto"},"relativeTime":{"future":{"one":"dentro de {0} minuto","other":"dentro de {0} minutos"},"past":{"one":"hace {0} minuto","other":"hace {0} minutos"}}},"minute-short":{"displayName":"min","relative":{"0":"este minuto"},"relativeTime":{"future":{"one":"dentro de {0} min","other":"dentro de {0} min"},"past":{"one":"hace {0} min","other":"hace {0} min"}}},"second":{"displayName":"segundo","relative":{"0":"ahora"},"relativeTime":{"future":{"one":"dentro de {0} segundo","other":"dentro de {0} segundos"},"past":{"one":"hace {0} segundo","other":"hace {0} segundos"}}},"second-short":{"displayName":"s","relative":{"0":"ahora"},"relativeTime":{"future":{"one":"dentro de {0} s","other":"dentro de {0} s"},"past":{"one":"hace {0} s","other":"hace {0} s"}}}} }); +addLocaleData({ locale: "es-LA", parentLocale: "es" }); +addLocaleData({ locale: "fr", pluralRuleFunction: function (n,ord){if(ord)return n==1?"one":"other";return n>=0&&n<2?"one":"other"},"fields":{"year":{"displayName":"annĆ©e","relative":{"0":"cette annĆ©e","1":"lā€™annĆ©e prochaine","-1":"lā€™annĆ©e derniĆØre"},"relativeTime":{"future":{"one":"dans {0} an","other":"dans {0} ans"},"past":{"one":"il y a {0} an","other":"il y a {0} ans"}}},"year-short":{"displayName":"an","relative":{"0":"cette annĆ©e","1":"lā€™annĆ©e prochaine","-1":"lā€™annĆ©e derniĆØre"},"relativeTime":{"future":{"one":"dans {0} a","other":"dans {0} a"},"past":{"one":"il y a {0} a","other":"il y a {0} a"}}},"month":{"displayName":"mois","relative":{"0":"ce mois-ci","1":"le mois prochain","-1":"le mois dernier"},"relativeTime":{"future":{"one":"dans {0} mois","other":"dans {0} mois"},"past":{"one":"il y a {0} mois","other":"il y a {0} mois"}}},"month-short":{"displayName":"m.","relative":{"0":"ce mois-ci","1":"le mois prochain","-1":"le mois dernier"},"relativeTime":{"future":{"one":"dans {0} m.","other":"dans {0} m."},"past":{"one":"il y a {0} m.","other":"il y a {0} m."}}},"day":{"displayName":"jour","relative":{"0":"aujourdā€™hui","1":"demain","2":"aprĆØs-demain","-2":"avant-hier","-1":"hier"},"relativeTime":{"future":{"one":"dans {0} jour","other":"dans {0} jours"},"past":{"one":"il y a {0} jour","other":"il y a {0} jours"}}},"day-short":{"displayName":"j","relative":{"0":"aujourdā€™hui","1":"demain","2":"aprĆØs-demain","-2":"avant-hier","-1":"hier"},"relativeTime":{"future":{"one":"dans {0} j","other":"dans {0} j"},"past":{"one":"il y a {0} j","other":"il y a {0} j"}}},"hour":{"displayName":"heure","relative":{"0":"cette heure-ci"},"relativeTime":{"future":{"one":"dans {0} heure","other":"dans {0} heures"},"past":{"one":"il y a {0} heure","other":"il y a {0} heures"}}},"hour-short":{"displayName":"h","relative":{"0":"cette heure-ci"},"relativeTime":{"future":{"one":"dans {0} h","other":"dans {0} h"},"past":{"one":"il y a {0} h","other":"il y a {0} h"}}},"minute":{"displayName":"minute","relative":{"0":"cette minute-ci"},"relativeTime":{"future":{"one":"dans {0} minute","other":"dans {0} minutes"},"past":{"one":"il y a {0} minute","other":"il y a {0} minutes"}}},"minute-short":{"displayName":"min","relative":{"0":"cette minute-ci"},"relativeTime":{"future":{"one":"dans {0} min","other":"dans {0} min"},"past":{"one":"il y a {0} min","other":"il y a {0} min"}}},"second":{"displayName":"seconde","relative":{"0":"maintenant"},"relativeTime":{"future":{"one":"dans {0} seconde","other":"dans {0} secondes"},"past":{"one":"il y a {0} seconde","other":"il y a {0} secondes"}}},"second-short":{"displayName":"s","relative":{"0":"maintenant"},"relativeTime":{"future":{"one":"dans {0} s","other":"dans {0} s"},"past":{"one":"il y a {0} s","other":"il y a {0} s"}}}} }); +addLocaleData({ locale: "fr-FR", parentLocale: "fr" }); +addLocaleData({ locale: "de", pluralRuleFunction: function (n,ord){var s=String(n).split("."),v0=!s[1];if(ord)return"other";return n==1&&v0?"one":"other"},"fields":{"year":{"displayName":"Jahr","relative":{"0":"dieses Jahr","1":"nƤchstes Jahr","-1":"letztes Jahr"},"relativeTime":{"future":{"one":"in {0} Jahr","other":"in {0} Jahren"},"past":{"one":"vor {0} Jahr","other":"vor {0} Jahren"}}},"year-short":{"displayName":"Jahr","relative":{"0":"dieses Jahr","1":"nƤchstes Jahr","-1":"letztes Jahr"},"relativeTime":{"future":{"one":"in {0} Jahr","other":"in {0} Jahren"},"past":{"one":"vor {0} Jahr","other":"vor {0} Jahren"}}},"month":{"displayName":"Monat","relative":{"0":"diesen Monat","1":"nƤchsten Monat","-1":"letzten Monat"},"relativeTime":{"future":{"one":"in {0} Monat","other":"in {0} Monaten"},"past":{"one":"vor {0} Monat","other":"vor {0} Monaten"}}},"month-short":{"displayName":"Monat","relative":{"0":"diesen Monat","1":"nƤchsten Monat","-1":"letzten Monat"},"relativeTime":{"future":{"one":"in {0} Monat","other":"in {0} Monaten"},"past":{"one":"vor {0} Monat","other":"vor {0} Monaten"}}},"day":{"displayName":"Tag","relative":{"0":"heute","1":"morgen","2":"Ć¼bermorgen","-2":"vorgestern","-1":"gestern"},"relativeTime":{"future":{"one":"in {0} Tag","other":"in {0} Tagen"},"past":{"one":"vor {0} Tag","other":"vor {0} Tagen"}}},"day-short":{"displayName":"Tag","relative":{"0":"heute","1":"morgen","2":"Ć¼bermorgen","-2":"vorgestern","-1":"gestern"},"relativeTime":{"future":{"one":"in {0} Tag","other":"in {0} Tagen"},"past":{"one":"vor {0} Tag","other":"vor {0} Tagen"}}},"hour":{"displayName":"Stunde","relative":{"0":"in dieser Stunde"},"relativeTime":{"future":{"one":"in {0} Stunde","other":"in {0} Stunden"},"past":{"one":"vor {0} Stunde","other":"vor {0} Stunden"}}},"hour-short":{"displayName":"Std.","relative":{"0":"in dieser Stunde"},"relativeTime":{"future":{"one":"in {0} Std.","other":"in {0} Std."},"past":{"one":"vor {0} Std.","other":"vor {0} Std."}}},"minute":{"displayName":"Minute","relative":{"0":"in dieser Minute"},"relativeTime":{"future":{"one":"in {0} Minute","other":"in {0} Minuten"},"past":{"one":"vor {0} Minute","other":"vor {0} Minuten"}}},"minute-short":{"displayName":"Min.","relative":{"0":"in dieser Minute"},"relativeTime":{"future":{"one":"in {0} Min.","other":"in {0} Min."},"past":{"one":"vor {0} Min.","other":"vor {0} Min."}}},"second":{"displayName":"Sekunde","relative":{"0":"jetzt"},"relativeTime":{"future":{"one":"in {0} Sekunde","other":"in {0} Sekunden"},"past":{"one":"vor {0} Sekunde","other":"vor {0} Sekunden"}}},"second-short":{"displayName":"Sek.","relative":{"0":"jetzt"},"relativeTime":{"future":{"one":"in {0} Sek.","other":"in {0} Sek."},"past":{"one":"vor {0} Sek.","other":"vor {0} Sek."}}}} }); +addLocaleData({ locale: "de-DE", parentLocale: "de" }); +addLocaleData({ locale: "ja", pluralRuleFunction: function (n,ord){if(ord)return"other";return"other"},"fields":{"year":{"displayName":"幓","relative":{"0":"今幓","1":"ēæŒå¹“","-1":"ę˜Ø幓"},"relativeTime":{"future":{"other":"{0} 幓後"},"past":{"other":"{0} 幓前"}}},"year-short":{"displayName":"幓","relative":{"0":"今幓","1":"ēæŒå¹“","-1":"ę˜Ø幓"},"relativeTime":{"future":{"other":"{0} 幓後"},"past":{"other":"{0} 幓前"}}},"month":{"displayName":"꜈","relative":{"0":"ä»Šęœˆ","1":"ēæŒęœˆ","-1":"å…ˆęœˆ"},"relativeTime":{"future":{"other":"{0} ć‹ęœˆå¾Œ"},"past":{"other":"{0} ć‹ęœˆå‰"}}},"month-short":{"displayName":"꜈","relative":{"0":"ä»Šęœˆ","1":"ēæŒęœˆ","-1":"å…ˆęœˆ"},"relativeTime":{"future":{"other":"{0} ć‹ęœˆå¾Œ"},"past":{"other":"{0} ć‹ęœˆå‰"}}},"day":{"displayName":"ę—„","relative":{"0":"今ꗄ","1":"ę˜Žę—„","2":"ę˜Žå¾Œę—„","-2":"äø€ę˜Øę—„","-1":"ę˜Øę—„"},"relativeTime":{"future":{"other":"{0} ę—„å¾Œ"},"past":{"other":"{0} ę—„å‰"}}},"day-short":{"displayName":"ę—„","relative":{"0":"今ꗄ","1":"ę˜Žę—„","2":"ę˜Žå¾Œę—„","-2":"äø€ę˜Øę—„","-1":"ę˜Øę—„"},"relativeTime":{"future":{"other":"{0} ę—„å¾Œ"},"past":{"other":"{0} ę—„å‰"}}},"hour":{"displayName":"Ꙃ","relative":{"0":"1 ę™‚é–“ä»„å†…"},"relativeTime":{"future":{"other":"{0} ę™‚é–“å¾Œ"},"past":{"other":"{0} ę™‚é–“å‰"}}},"hour-short":{"displayName":"Ꙃ","relative":{"0":"1 ę™‚é–“ä»„å†…"},"relativeTime":{"future":{"other":"{0} ę™‚é–“å¾Œ"},"past":{"other":"{0} ę™‚é–“å‰"}}},"minute":{"displayName":"分","relative":{"0":"1 分仄内"},"relativeTime":{"future":{"other":"{0} 分後"},"past":{"other":"{0} 分前"}}},"minute-short":{"displayName":"分","relative":{"0":"1 分仄内"},"relativeTime":{"future":{"other":"{0} 分後"},"past":{"other":"{0} 分前"}}},"second":{"displayName":"ē§’","relative":{"0":"今"},"relativeTime":{"future":{"other":"{0} ē§’後"},"past":{"other":"{0} ē§’前"}}},"second-short":{"displayName":"ē§’","relative":{"0":"今"},"relativeTime":{"future":{"other":"{0} ē§’後"},"past":{"other":"{0} ē§’前"}}}} }); +addLocaleData({ locale: "ja-JP", parentLocale: "ja" }); +addLocaleData({ locale: "ko", pluralRuleFunction: function (n,ord){if(ord)return"other";return"other"},"fields":{"year":{"displayName":"ė…„","relative":{"0":"ģ˜¬ķ•“","1":"ė‚“ė…„","-1":"ģž‘ė…„"},"relativeTime":{"future":{"other":"{0}ė…„ ķ›„"},"past":{"other":"{0}ė…„ ģ „"}}},"year-short":{"displayName":"ė…„","relative":{"0":"ģ˜¬ķ•“","1":"ė‚“ė…„","-1":"ģž‘ė…„"},"relativeTime":{"future":{"other":"{0}ė…„ ķ›„"},"past":{"other":"{0}ė…„ ģ „"}}},"month":{"displayName":"ģ›”","relative":{"0":"ģ“ė²ˆ ė‹¬","1":"ė‹¤ģŒ ė‹¬","-1":"ģ§€ė‚œė‹¬"},"relativeTime":{"future":{"other":"{0}ź°œģ›” ķ›„"},"past":{"other":"{0}ź°œģ›” ģ „"}}},"month-short":{"displayName":"ģ›”","relative":{"0":"ģ“ė²ˆ ė‹¬","1":"ė‹¤ģŒ ė‹¬","-1":"ģ§€ė‚œė‹¬"},"relativeTime":{"future":{"other":"{0}ź°œģ›” ķ›„"},"past":{"other":"{0}ź°œģ›” ģ „"}}},"day":{"displayName":"ģ¼","relative":{"0":"ģ˜¤ėŠ˜","1":"ė‚“ģ¼","2":"ėŖØė ˆ","-2":"ź·øģ €ź»˜","-1":"ģ–“ģ œ"},"relativeTime":{"future":{"other":"{0}ģ¼ ķ›„"},"past":{"other":"{0}ģ¼ ģ „"}}},"day-short":{"displayName":"ģ¼","relative":{"0":"ģ˜¤ėŠ˜","1":"ė‚“ģ¼","2":"ėŖØė ˆ","-2":"ź·øģ €ź»˜","-1":"ģ–“ģ œ"},"relativeTime":{"future":{"other":"{0}ģ¼ ķ›„"},"past":{"other":"{0}ģ¼ ģ „"}}},"hour":{"displayName":"ģ‹œ","relative":{"0":"ķ˜„ģž¬ ģ‹œź°„"},"relativeTime":{"future":{"other":"{0}ģ‹œź°„ ķ›„"},"past":{"other":"{0}ģ‹œź°„ ģ „"}}},"hour-short":{"displayName":"ģ‹œ","relative":{"0":"ķ˜„ģž¬ ģ‹œź°„"},"relativeTime":{"future":{"other":"{0}ģ‹œź°„ ķ›„"},"past":{"other":"{0}ģ‹œź°„ ģ „"}}},"minute":{"displayName":"ė¶„","relative":{"0":"ķ˜„ģž¬ ė¶„"},"relativeTime":{"future":{"other":"{0}ė¶„ ķ›„"},"past":{"other":"{0}ė¶„ ģ „"}}},"minute-short":{"displayName":"ė¶„","relative":{"0":"ķ˜„ģž¬ ė¶„"},"relativeTime":{"future":{"other":"{0}ė¶„ ķ›„"},"past":{"other":"{0}ė¶„ ģ „"}}},"second":{"displayName":"ģ“ˆ","relative":{"0":"ģ§€źøˆ"},"relativeTime":{"future":{"other":"{0}ģ“ˆ ķ›„"},"past":{"other":"{0}ģ“ˆ ģ „"}}},"second-short":{"displayName":"ģ“ˆ","relative":{"0":"ģ§€źøˆ"},"relativeTime":{"future":{"other":"{0}ģ“ˆ ķ›„"},"past":{"other":"{0}ģ“ˆ ģ „"}}}} }); +addLocaleData({ locale: "ko-KR", parentLocale: "ko" }); +addLocaleData({ locale: 'ru', pluralRuleFunction: function (n,ord){var s=String(n).split("."),i=s[0],v0=!s[1],i10=i.slice(-1),i100=i.slice(-2);if(ord)return"other";return v0&&i10==1&&i100!=11?"one":v0&&i10>=2&&i10<=4&&(i100<12||i100>14)?"few":(v0&&i10==0)||(v0&&i10>=5&&i10<=9)||(v0&&i100>=11&&i100<=14)?"many":"other";},"fields":{"year":{"displayName":"Š³Š¾Š“","relative":{"0":"Š² этŠ¾Š¼ Š³Š¾Š“у","1":"Š² сŠ»ŠµŠ“ующŠµŠ¼ Š³Š¾Š“у","-1":"Š² ŠæрŠ¾ŃˆŠ»Š¾Š¼ Š³Š¾Š“у"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š³Š¾Š“","few":"чŠµŃ€ŠµŠ· {0} Š³Š¾Š“Š°","many":"чŠµŃ€ŠµŠ· {0} Š»ŠµŃ‚","other":"чŠµŃ€ŠµŠ· {0} Š³Š¾Š“Š°"},"past":{"one":"{0} Š³Š¾Š“ Š½Š°Š·Š°Š“","few":"{0} Š³Š¾Š“Š° Š½Š°Š·Š°Š“","many":"{0} Š»ŠµŃ‚ Š½Š°Š·Š°Š“","other":"{0} Š³Š¾Š“Š° Š½Š°Š·Š°Š“"}}},"year-short":{"displayName":"Š³.","relative":{"0":"Š² этŠ¾Š¼ Š³.","1":"Š² сŠ»ŠµŠ“. Š³.","-1":"Š² ŠæрŠ¾ŃˆŠ»Š¾Š¼ Š³."},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š³.","few":"чŠµŃ€ŠµŠ· {0} Š³.","many":"чŠµŃ€ŠµŠ· {0} Š».","other":"чŠµŃ€ŠµŠ· {0} Š³."},"past":{"one":"{0} Š³. Š½Š°Š·Š°Š“","few":"{0} Š³. Š½Š°Š·Š°Š“","many":"{0} Š». Š½Š°Š·Š°Š“","other":"{0} Š³. Š½Š°Š·Š°Š“"}}},"month":{"displayName":"Š¼ŠµŃŃŃ†","relative":{"0":"Š² этŠ¾Š¼ Š¼ŠµŃŃŃ†Šµ","1":"Š² сŠ»ŠµŠ“ующŠµŠ¼ Š¼ŠµŃŃŃ†Šµ","-1":"Š² ŠæрŠ¾ŃˆŠ»Š¾Š¼ Š¼ŠµŃŃŃ†Šµ"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃŃŃ†","few":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃŃŃ†Š°","many":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃŃŃ†ŠµŠ²","other":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃŃŃ†Š°"},"past":{"one":"{0} Š¼ŠµŃŃŃ† Š½Š°Š·Š°Š“","few":"{0} Š¼ŠµŃŃŃ†Š° Š½Š°Š·Š°Š“","many":"{0} Š¼ŠµŃŃŃ†ŠµŠ² Š½Š°Š·Š°Š“","other":"{0} Š¼ŠµŃŃŃ†Š° Š½Š°Š·Š°Š“"}}},"month-short":{"displayName":"Š¼ŠµŃ.","relative":{"0":"Š² этŠ¾Š¼ Š¼ŠµŃ.","1":"Š² сŠ»ŠµŠ“ующŠµŠ¼ Š¼ŠµŃ.","-1":"Š² ŠæрŠ¾ŃˆŠ»Š¾Š¼ Š¼ŠµŃ."},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃ.","few":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃ.","many":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃ.","other":"чŠµŃ€ŠµŠ· {0} Š¼ŠµŃ."},"past":{"one":"{0} Š¼ŠµŃ. Š½Š°Š·Š°Š“","few":"{0} Š¼ŠµŃ. Š½Š°Š·Š°Š“","many":"{0} Š¼ŠµŃ. Š½Š°Š·Š°Š“","other":"{0} Š¼ŠµŃ. Š½Š°Š·Š°Š“"}}},"week":{"displayName":"Š½ŠµŠ“ŠµŠ»Ń","relativePeriod":"Š½Š° Š½ŠµŠ“ŠµŠ»Šµ {0}","relative":{"0":"Š½Š° этŠ¾Š¹ Š½ŠµŠ“ŠµŠ»Šµ","1":"Š½Š° сŠ»ŠµŠ“ующŠµŠ¹ Š½ŠµŠ“ŠµŠ»Šµ","-1":"Š½Š° ŠæрŠ¾ŃˆŠ»Š¾Š¹ Š½ŠµŠ“ŠµŠ»Šµ"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“ŠµŠ»ŃŽ","few":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“ŠµŠ»Šø","many":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“ŠµŠ»ŃŒ","other":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“ŠµŠ»Šø"},"past":{"one":"{0} Š½ŠµŠ“ŠµŠ»ŃŽ Š½Š°Š·Š°Š“","few":"{0} Š½ŠµŠ“ŠµŠ»Šø Š½Š°Š·Š°Š“","many":"{0} Š½ŠµŠ“ŠµŠ»ŃŒ Š½Š°Š·Š°Š“","other":"{0} Š½ŠµŠ“ŠµŠ»Šø Š½Š°Š·Š°Š“"}}},"week-short":{"displayName":"Š½ŠµŠ“.","relativePeriod":"Š½Š° Š½ŠµŠ“. {0}","relative":{"0":"Š½Š° этŠ¾Š¹ Š½ŠµŠ“.","1":"Š½Š° сŠ»ŠµŠ“ующŠµŠ¹ Š½ŠµŠ“.","-1":"Š½Š° ŠæрŠ¾ŃˆŠ»Š¾Š¹ Š½ŠµŠ“."},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“.","few":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“.","many":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“.","other":"чŠµŃ€ŠµŠ· {0} Š½ŠµŠ“."},"past":{"one":"{0} Š½ŠµŠ“. Š½Š°Š·Š°Š“","few":"{0} Š½ŠµŠ“. Š½Š°Š·Š°Š“","many":"{0} Š½ŠµŠ“. Š½Š°Š·Š°Š“","other":"{0} Š½ŠµŠ“. Š½Š°Š·Š°Š“"}}},"day":{"displayName":"Š“ŠµŠ½ŃŒ","relative":{"0":"сŠµŠ³Š¾Š“Š½Ń","1":"Š·Š°Š²Ń‚Ń€Š°","2":"ŠæŠ¾ŃŠ»ŠµŠ·Š°Š²Ń‚Ń€Š°","-1":"Š²Ń‡ŠµŃ€Š°","-2":"ŠæŠ¾Š·Š°Š²Ń‡ŠµŃ€Š°"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š“ŠµŠ½ŃŒ","few":"чŠµŃ€ŠµŠ· {0} Š“Š½Ń","many":"чŠµŃ€ŠµŠ· {0} Š“Š½ŠµŠ¹","other":"чŠµŃ€ŠµŠ· {0} Š“Š½Ń"},"past":{"one":"{0} Š“ŠµŠ½ŃŒ Š½Š°Š·Š°Š“","few":"{0} Š“Š½Ń Š½Š°Š·Š°Š“","many":"{0} Š“Š½ŠµŠ¹ Š½Š°Š·Š°Š“","other":"{0} Š“Š½Ń Š½Š°Š·Š°Š“"}}},"day-short":{"displayName":"Š“Š½.","relative":{"0":"сŠµŠ³Š¾Š“Š½Ń","1":"Š·Š°Š²Ń‚Ń€Š°","2":"ŠæŠ¾ŃŠ»ŠµŠ·Š°Š²Ń‚Ń€Š°","-1":"Š²Ń‡ŠµŃ€Š°","-2":"ŠæŠ¾Š·Š°Š²Ń‡ŠµŃ€Š°"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š“Š½.","few":"чŠµŃ€ŠµŠ· {0} Š“Š½.","many":"чŠµŃ€ŠµŠ· {0} Š“Š½.","other":"чŠµŃ€ŠµŠ· {0} Š“Š½."},"past":{"one":"{0} Š“Š½. Š½Š°Š·Š°Š“","few":"{0} Š“Š½. Š½Š°Š·Š°Š“","many":"{0} Š“Š½. Š½Š°Š·Š°Š“","other":"{0} Š“Š½. Š½Š°Š·Š°Š“"}}},"hour":{"displayName":"чŠ°Ń","relative":{"0":"Š² этŠ¾Ń‚ чŠ°Ń"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} чŠ°Ń","few":"чŠµŃ€ŠµŠ· {0} чŠ°ŃŠ°","many":"чŠµŃ€ŠµŠ· {0} чŠ°ŃŠ¾Š²","other":"чŠµŃ€ŠµŠ· {0} чŠ°ŃŠ°"},"past":{"one":"{0} чŠ°Ń Š½Š°Š·Š°Š“","few":"{0} чŠ°ŃŠ° Š½Š°Š·Š°Š“","many":"{0} чŠ°ŃŠ¾Š² Š½Š°Š·Š°Š“","other":"{0} чŠ°ŃŠ° Š½Š°Š·Š°Š“"}}},"hour-short":{"displayName":"ч","relative":{"0":"Š² этŠ¾Ń‚ чŠ°Ń"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} ч.","few":"чŠµŃ€ŠµŠ· {0} ч.","many":"чŠµŃ€ŠµŠ· {0} ч.","other":"чŠµŃ€ŠµŠ· {0} ч."},"past":{"one":"{0} ч. Š½Š°Š·Š°Š“","few":"{0} ч. Š½Š°Š·Š°Š“","many":"{0} ч. Š½Š°Š·Š°Š“","other":"{0} ч. Š½Š°Š·Š°Š“"}}},"minute":{"displayName":"Š¼ŠøŠ½ŃƒŃ‚Š°","relative":{"0":"Š² эту Š¼ŠøŠ½ŃƒŃ‚Ńƒ"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½ŃƒŃ‚Ńƒ","few":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½ŃƒŃ‚Ń‹","many":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½ŃƒŃ‚","other":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½ŃƒŃ‚Ń‹"},"past":{"one":"{0} Š¼ŠøŠ½ŃƒŃ‚Ńƒ Š½Š°Š·Š°Š“","few":"{0} Š¼ŠøŠ½ŃƒŃ‚Ń‹ Š½Š°Š·Š°Š“","many":"{0} Š¼ŠøŠ½ŃƒŃ‚ Š½Š°Š·Š°Š“","other":"{0} Š¼ŠøŠ½ŃƒŃ‚Ń‹ Š½Š°Š·Š°Š“"}}},"minute-short":{"displayName":"Š¼ŠøŠ½.","relative":{"0":"Š² эту Š¼ŠøŠ½ŃƒŃ‚Ńƒ"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½.","few":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½.","many":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½.","other":"чŠµŃ€ŠµŠ· {0} Š¼ŠøŠ½."},"past":{"one":"{0} Š¼ŠøŠ½. Š½Š°Š·Š°Š“","few":"{0} Š¼ŠøŠ½. Š½Š°Š·Š°Š“","many":"{0} Š¼ŠøŠ½. Š½Š°Š·Š°Š“","other":"{0} Š¼ŠøŠ½. Š½Š°Š·Š°Š“"}}},"second":{"displayName":"сŠµŠŗуŠ½Š“Š°","relative":{"0":"сŠµŠ¹Ń‡Š°Ń"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} сŠµŠŗуŠ½Š“у","few":"чŠµŃ€ŠµŠ· {0} сŠµŠŗуŠ½Š“ы","many":"чŠµŃ€ŠµŠ· {0} сŠµŠŗуŠ½Š“","other":"чŠµŃ€ŠµŠ· {0} сŠµŠŗуŠ½Š“ы"},"past":{"one":"{0} сŠµŠŗуŠ½Š“у Š½Š°Š·Š°Š“","few":"{0} сŠµŠŗуŠ½Š“ы Š½Š°Š·Š°Š“","many":"{0} сŠµŠŗуŠ½Š“ Š½Š°Š·Š°Š“","other":"{0} сŠµŠŗуŠ½Š“ы Š½Š°Š·Š°Š“"}}},"second-short":{"displayName":"сŠµŠŗ.","relative":{"0":"сŠµŠ¹Ń‡Š°Ń"},"relativeTime":{"future":{"one":"чŠµŃ€ŠµŠ· {0} сŠµŠŗ.","few":"чŠµŃ€ŠµŠ· {0} сŠµŠŗ.","many":"чŠµŃ€ŠµŠ· {0} сŠµŠŗ.","other":"чŠµŃ€ŠµŠ· {0} сŠµŠŗ."},"past":{"one":"{0} сŠµŠŗ. Š½Š°Š·Š°Š“","few":"{0} сŠµŠŗ. Š½Š°Š·Š°Š“","many":"{0} сŠµŠŗ. Š½Š°Š·Š°Š“","other":"{0} сŠµŠŗ. Š½Š°Š·Š°Š“"}}}} }); +addLocaleData({ locale: "ru-RU", parentLocale: "ru" }); +addLocaleData({ locale: "zh", pluralRuleFunction: function (n,ord){if(ord)return"other";return"other"},"fields":{"year":{"displayName":"幓","relative":{"0":"今幓","1":"ę˜Žå¹“","-1":"去幓"},"relativeTime":{"future":{"other":"{0}幓后"},"past":{"other":"{0}幓前"}}},"year-short":{"displayName":"幓","relative":{"0":"今幓","1":"ę˜Žå¹“","-1":"去幓"},"relativeTime":{"future":{"other":"{0}幓后"},"past":{"other":"{0}幓前"}}},"month":{"displayName":"꜈","relative":{"0":"ęœ¬ęœˆ","1":"äø‹äøŖ꜈","-1":"äøŠäøŖ꜈"},"relativeTime":{"future":{"other":"{0}äøŖęœˆåŽ"},"past":{"other":"{0}äøŖęœˆå‰"}}},"month-short":{"displayName":"꜈","relative":{"0":"ęœ¬ęœˆ","1":"äø‹äøŖ꜈","-1":"äøŠäøŖ꜈"},"relativeTime":{"future":{"other":"{0}äøŖęœˆåŽ"},"past":{"other":"{0}äøŖęœˆå‰"}}},"day":{"displayName":"ę—„","relative":{"0":"今天","1":"ę˜Žå¤©","2":"后天","-2":"前天","-1":"ę˜Ø天"},"relativeTime":{"future":{"other":"{0}天后"},"past":{"other":"{0}天前"}}},"day-short":{"displayName":"ę—„","relative":{"0":"今天","1":"ę˜Žå¤©","2":"后天","-2":"前天","-1":"ę˜Ø天"},"relativeTime":{"future":{"other":"{0}天后"},"past":{"other":"{0}天前"}}},"hour":{"displayName":"å°ę—¶","relative":{"0":"čæ™äø€ę—¶é—“ \u002F ꭤꗶ"},"relativeTime":{"future":{"other":"{0}å°ę—¶åŽ"},"past":{"other":"{0}å°ę—¶å‰"}}},"hour-short":{"displayName":"å°ę—¶","relative":{"0":"čæ™äø€ę—¶é—“ \u002F ꭤꗶ"},"relativeTime":{"future":{"other":"{0}å°ę—¶åŽ"},"past":{"other":"{0}å°ę—¶å‰"}}},"minute":{"displayName":"分钟","relative":{"0":"ę­¤åˆ»"},"relativeTime":{"future":{"other":"{0}分钟后"},"past":{"other":"{0}分钟前"}}},"minute-short":{"displayName":"分","relative":{"0":"ę­¤åˆ»"},"relativeTime":{"future":{"other":"{0}分钟后"},"past":{"other":"{0}分钟前"}}},"second":{"displayName":"ē§’","relative":{"0":"ēŽ°åœØ"},"relativeTime":{"future":{"other":"{0}ē§’钟后"},"past":{"other":"{0}ē§’钟前"}}},"second-short":{"displayName":"ē§’","relative":{"0":"ēŽ°åœØ"},"relativeTime":{"future":{"other":"{0}ē§’后"},"past":{"other":"{0}ē§’前"}}}} }); +addLocaleData({ locale: "zh-CN", parentLocale: "zh" }); diff --git a/plugins/main/public/kibana-integrations/packages/osd-i18n/core/pseudo_locale.ts b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/pseudo_locale.ts new file mode 100644 index 0000000000..02affa92c4 --- /dev/null +++ b/plugins/main/public/kibana-integrations/packages/osd-i18n/core/pseudo_locale.ts @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Matches every single [A-Za-z] character, ``, `](markdown-link-address)` and `@I18N@valid_variable_name@I18N@` + */ +const CHARS_FOR_PSEUDO_LOCALIZATION_REGEX = /[A-Za-z]|(\]\([\s\S]*?\))|(<([^"<>]|("[^"]*?"))*?>)|(@I18N@\w*?@I18N@)/g; +const PSEUDO_ACCENTS_LOCALE = 'en-xa'; + +export function isPseudoLocale(locale: string) { + return locale.toLowerCase() === PSEUDO_ACCENTS_LOCALE; +} + +/** + * Replaces every latin char by pseudo char and repeats every third char twice. + */ +function replacer() { + let count = 0; + + return (match: string) => { + // if `match.length !== 1`, then `match` is html tag or markdown link address, so it should be ignored + if (match.length !== 1) { + return match; + } + + const pseudoChar = pseudoAccentCharMap[match] || match; + return ++count % 3 === 0 ? pseudoChar.repeat(2) : pseudoChar; + }; +} + +export function translateUsingPseudoLocale(message: string) { + return message.replace(CHARS_FOR_PSEUDO_LOCALIZATION_REGEX, replacer()); +} + +const pseudoAccentCharMap: Record = { + a: 'Ć ', + b: 'ʀ', + c: 'Ƨ', + d: 'Ć°', + e: 'Ć©', + f: 'ʒ', + g: 'ĝ', + h: 'Ä„', + i: 'Ć®', + l: 'ļ', + k: 'Ä·', + j: 'ĵ', + m: 'ɱ', + n: 'Ʊ', + o: 'Ć“', + p: 'Ć¾', + q: 'Ē«', + r: 'ŕ', + s: 'Å”', + t: 'Å£', + u: 'Ć»', + v: 'į¹½', + w: 'ŵ', + x: 'įŗ‹', + y: 'Ć½', + z: 'ž', + A: 'ƀ', + B: 'ʁ', + C: 'Ƈ', + D: 'Ɛ', + E: 'Ɖ', + F: 'ʑ', + G: 'Ĝ', + H: 'Ĥ', + I: 'Ǝ', + L: 'Ä»', + K: 'Ķ', + J: 'Ä“', + M: 'į¹€', + N: 'Ƒ', + O: 'Ɣ', + P: 'ƞ', + Q: 'ĒŖ', + R: 'Ŕ', + S: 'Å ', + T: 'Å¢', + U: 'ƛ', + V: 'į¹¼', + W: 'Å“', + X: 'įŗŠ', + Y: 'Ɲ', + Z: 'Ž', +}; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/angular_config.tsx b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/angular_config.tsx new file mode 100644 index 0000000000..fbe36a289d --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/angular_config.tsx @@ -0,0 +1,380 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + ICompileProvider, + IHttpProvider, + IHttpService, + ILocationProvider, + IModule, + IRootScopeService, +} from 'angular'; +import $ from 'jquery'; +import { set } from '@elastic/safer-lodash-set'; +import { get } from 'lodash'; +import * as Rx from 'rxjs'; +import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'opensearch-dashboards/public'; +import { History } from 'history'; + +import { CoreStart } from 'opensearch-dashboards/public'; +import { isSystemApiRequest } from '../utils'; +import { formatAngularHttpError, isAngularHttpError } from '../notify/lib'; + +export interface RouteConfiguration { + controller?: string | ((...args: any[]) => void); + redirectTo?: string; + resolveRedirectTo?: (...args: any[]) => void; + reloadOnSearch?: boolean; + reloadOnUrl?: boolean; + outerAngularWrapperRoute?: boolean; + resolve?: object; + template?: string; + k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; + requireUICapability?: string; +} + +/** + * Detects whether a given angular route is a dummy route that doesn't + * require any action. There are two ways this can happen: + * If `outerAngularWrapperRoute` is set on the route config object, + * it means the local application service set up this route on the outer angular + * and the internal routes will handle the hooks. + * + * If angular did not detect a route and it is the local angular, we are currently + * navigating away from a URL controlled by a local angular router and the + * application will get unmounted. In this case the outer router will handle + * the hooks. + * @param $route Injected $route dependency + * @param isLocalAngular Flag whether this is the local angular router + */ +function isDummyRoute($route: any, isLocalAngular: boolean) { + return ( + ($route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute) || + (!$route.current && isLocalAngular) + ); +} + +export const configureAppAngularModule = ( + angularModule: IModule, + newPlatform: { + core: CoreStart; + readonly env: { + mode: Readonly; + packageInfo: Readonly; + }; + }, + isLocalAngular: boolean, + getHistory?: () => History +) => { + const core = 'core' in newPlatform ? newPlatform.core : newPlatform; + const packageInfo = newPlatform.env.packageInfo; + + angularModule + .value('osdVersion', packageInfo.version) + .value('buildNum', packageInfo.buildNum) + .value('buildSha', packageInfo.buildSha) + .value('opensearchUrl', getOpenSearchUrl(core)) + .value('uiCapabilities', core.application.capabilities) + .config(setupCompileProvider(newPlatform.env.mode.dev)) + .config(setupLocationProvider()) + .config($setupXsrfRequestInterceptor(packageInfo.version)) + .run(capture$httpLoadingCount(core)) + .run(digestOnHashChange(getHistory)) + .run($setupBreadcrumbsAutoClear(core, isLocalAngular)) + .run($setupBadgeAutoClear(core, isLocalAngular)) + .run($setupHelpExtensionAutoClear(core, isLocalAngular)) + .run($setupUICapabilityRedirect(core)); +}; + +const getOpenSearchUrl = (newPlatform: CoreStart) => { + const a = document.createElement('a'); + a.href = newPlatform.http.basePath.prepend('/opensearch'); + const protocolPort = /https/.test(a.protocol) ? 443 : 80; + const port = a.port || protocolPort; + return { + host: a.hostname, + port, + protocol: a.protocol, + pathname: a.pathname, + }; +}; + +const digestOnHashChange = (getHistory?: () => History) => ($rootScope: IRootScopeService) => { + if (!getHistory) return; + const unlisten = getHistory().listen(() => { + // dispatch synthetic hash change event to update hash history objects and angular routing + // this is necessary because hash updates triggered by using popState won't trigger this event naturally. + // this has to happen in the next tick to not change the existing timing of angular digest cycles. + setTimeout(() => { + window.dispatchEvent(new HashChangeEvent('hashchange')); + }, 0); + }); + $rootScope.$on('$destroy', unlisten); +}; + +const setupCompileProvider = (devMode: boolean) => ($compileProvider: ICompileProvider) => { + if (!devMode) { + $compileProvider.debugInfoEnabled(false); + } +}; + +const setupLocationProvider = () => ($locationProvider: ILocationProvider) => { + $locationProvider.html5Mode({ + enabled: false, + requireBase: false, + rewriteLinks: false, + }); + + $locationProvider.hashPrefix(''); +}; + +export const $setupXsrfRequestInterceptor = (version: string) => { + // Configure jQuery prefilter + $.ajaxPrefilter(({ osdXsrfToken = true }: any, originalOptions, jqXHR) => { + if (osdXsrfToken) { + jqXHR.setRequestHeader('osd-xsrf', 'osd-legacy'); + // ToDo: Remove next; `osd-version` incorrectly used for satisfying XSRF protection + jqXHR.setRequestHeader('osd-version', version); + } + }); + + return ($httpProvider: IHttpProvider) => { + // Configure $httpProvider interceptor + $httpProvider.interceptors.push(() => { + return { + request(opts) { + const { osdXsrfToken = true } = opts as any; + if (osdXsrfToken) { + set(opts, ['headers', 'osd-xsrf'], 'osd-legacy'); + // ToDo: Remove next; `osd-version` incorrectly used for satisfying XSRF protection + set(opts, ['headers', 'osd-version'], version); + } + return opts; + }, + }; + }); + }; +}; + +/** + * Injected into angular module by ui/chrome angular integration + * and adds a root-level watcher that will capture the count of + * active $http requests on each digest loop and expose the count to + * the core.loadingCount api + */ +const capture$httpLoadingCount = (newPlatform: CoreStart) => ( + $rootScope: IRootScopeService, + $http: IHttpService +) => { + newPlatform.http.addLoadingCountSource( + new Rx.Observable((observer) => { + const unwatch = $rootScope.$watch(() => { + const reqs = $http.pendingRequests || []; + observer.next(reqs.filter((req) => !isSystemApiRequest(req)).length); + }); + + return unwatch; + }) + ); +}; + +/** + * integrates with angular to automatically redirect to home if required + * capability is not met + */ +const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + const isOpenSearchDashboardsAppRoute = window.location.pathname.endsWith( + '/app/opensearch-dashboards' + ); + // this feature only works within opensearch dashboards app for now after everything is + // switched to the application service, this can be changed to handle all + // apps. + if (!isOpenSearchDashboardsAppRoute) { + return; + } + $rootScope.$on( + '$routeChangeStart', + (event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => { + if (!route || !route.requireUICapability) { + return; + } + + if (!get(newPlatform.application.capabilities, route.requireUICapability)) { + $injector.get('$location').url('/home'); + event.preventDefault(); + } + } + ); +}; + +/** + * internal angular run function that will be called when angular bootstraps and + * lets us integrate with the angular router so that we can automatically clear + * the breadcrumbs if we switch to a OpenSearch Dashboards app that does not use breadcrumbs correctly + */ +const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + // A flag used to determine if we should automatically + // clear the breadcrumbs between angular route changes. + let breadcrumbSetSinceRouteChange = false; + const $route = $injector.has('$route') ? $injector.get('$route') : {}; + + // reset breadcrumbSetSinceRouteChange any time the breadcrumbs change, even + // if it was done directly through the new platform + newPlatform.chrome.getBreadcrumbs$().subscribe({ + next() { + breadcrumbSetSinceRouteChange = true; + }, + }); + + $rootScope.$on('$routeChangeStart', () => { + breadcrumbSetSinceRouteChange = false; + }); + + $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyRoute($route, isLocalAngular)) { + return; + } + const current = $route.current || {}; + + if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { + return; + } + + const k7BreadcrumbsProvider = current.k7Breadcrumbs; + if (!k7BreadcrumbsProvider) { + newPlatform.chrome.setBreadcrumbs([]); + return; + } + + try { + newPlatform.chrome.setBreadcrumbs($injector.invoke(k7BreadcrumbsProvider)); + } catch (error) { + if (isAngularHttpError(error)) { + error = formatAngularHttpError(error); + } + newPlatform.fatalErrors.add(error, 'location'); + } + }); +}; + +/** + * internal angular run function that will be called when angular bootstraps and + * lets us integrate with the angular router so that we can automatically clear + * the badge if we switch to a OpenSearch Dashboards app that does not use the badge correctly + */ +const $setupBadgeAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + // A flag used to determine if we should automatically + // clear the badge between angular route changes. + let badgeSetSinceRouteChange = false; + const $route = $injector.has('$route') ? $injector.get('$route') : {}; + + $rootScope.$on('$routeChangeStart', () => { + badgeSetSinceRouteChange = false; + }); + + $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyRoute($route, isLocalAngular)) { + return; + } + const current = $route.current || {}; + + if (badgeSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { + return; + } + + const badgeProvider = current.badge; + if (!badgeProvider) { + newPlatform.chrome.setBadge(undefined); + return; + } + + try { + newPlatform.chrome.setBadge($injector.invoke(badgeProvider)); + } catch (error) { + if (isAngularHttpError(error)) { + error = formatAngularHttpError(error); + } + newPlatform.fatalErrors.add(error, 'location'); + } + }); +}; + +/** + * internal angular run function that will be called when angular bootstraps and + * lets us integrate with the angular router so that we can automatically clear + * the helpExtension if we switch to a OpenSearch Dashboards app that does not set its own + * helpExtension + */ +const $setupHelpExtensionAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + /** + * reset helpExtensionSetSinceRouteChange any time the helpExtension changes, even + * if it was done directly through the new platform + */ + let helpExtensionSetSinceRouteChange = false; + newPlatform.chrome.getHelpExtension$().subscribe({ + next() { + helpExtensionSetSinceRouteChange = true; + }, + }); + + const $route = $injector.has('$route') ? $injector.get('$route') : {}; + + $rootScope.$on('$routeChangeStart', () => { + if (isDummyRoute($route, isLocalAngular)) { + return; + } + helpExtensionSetSinceRouteChange = false; + }); + + $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyRoute($route, isLocalAngular)) { + return; + } + const current = $route.current || {}; + + if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { + return; + } + + newPlatform.chrome.setHelpExtension(current.helpExtension); + }); +}; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/index.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/index.ts new file mode 100644 index 0000000000..c492de5100 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/index.ts @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +export { PromiseServiceCreator } from './promises'; +// @ts-ignore +export { watchMultiDecorator } from './watch_multi'; +export * from './angular_config'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper, loadOsdTopNavDirectives } from './osd_top_nav'; +export { subscribeWithScope } from './subscribe_with_scope'; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/osd_top_nav.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/osd_top_nav.js new file mode 100644 index 0000000000..11835005b6 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/osd_top_nav.js @@ -0,0 +1,140 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import angular from 'angular'; +import 'ngreact'; + +export function createTopNavDirective() { + return { + restrict: 'E', + template: '', + compile: (elem) => { + const child = document.createElement('osd-top-nav-helper'); + + // Copy attributes to the child directive + for (const attr of elem[0].attributes) { + child.setAttribute(attr.name, attr.value); + } + + // Add a special attribute that will change every time that one + // of the config array's disableButton function return value changes. + child.setAttribute('disabled-buttons', 'disabledButtons'); + + // Append helper directive + elem.append(child); + + const linkFn = ($scope, _, $attr) => { + // Watch config changes + $scope.$watch( + () => { + const config = $scope.$eval($attr.config) || []; + return config.map((item) => { + // Copy key into id, as it's a reserved react propery. + // This is done for Angular directive backward compatibility. + // In React only id is recognized. + if (item.key && !item.id) { + item.id = item.key; + } + + // Watch the disableButton functions + if (typeof item.disableButton === 'function') { + return item.disableButton(); + } + return item.disableButton; + }); + }, + (newVal) => { + $scope.disabledButtons = newVal; + }, + true + ); + }; + + return linkFn; + }, + }; +} + +export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => { + return reactDirective(TopNavMenu, [ + ['config', { watchDepth: 'value' }], + ['setMenuMountPoint', { watchDepth: 'reference' }], + ['disabledButtons', { watchDepth: 'reference' }], + + ['query', { watchDepth: 'reference' }], + ['savedQuery', { watchDepth: 'reference' }], + ['intl', { watchDepth: 'reference' }], + + ['onQuerySubmit', { watchDepth: 'reference' }], + ['onFiltersUpdated', { watchDepth: 'reference' }], + ['onRefreshChange', { watchDepth: 'reference' }], + ['onClearSavedQuery', { watchDepth: 'reference' }], + ['onSaved', { watchDepth: 'reference' }], + ['onSavedQueryUpdated', { watchDepth: 'reference' }], + ['onSavedQueryIdChange', { watchDepth: 'reference' }], + + ['indexPatterns', { watchDepth: 'collection' }], + ['filters', { watchDepth: 'collection' }], + + // All modifiers default to true. + // Set to false to hide subcomponents. + 'showSearchBar', + 'showQueryBar', + 'showQueryInput', + 'showSaveQuery', + 'showDatePicker', + 'showFilterBar', + + 'appName', + 'screenTitle', + 'dateRangeFrom', + 'dateRangeTo', + 'savedQueryId', + 'isRefreshPaused', + 'refreshInterval', + 'disableAutoFocus', + 'showAutoRefreshOnly', + + // temporary flag to use the stateful components + 'useDefaultBehaviors', + ]); +}; + +let isLoaded = false; + +export function loadOsdTopNavDirectives(navUi) { + if (!isLoaded) { + isLoaded = true; + angular + .module('opensearchDashboards') + .directive('osdTopNav', createTopNavDirective) + .directive('osdTopNavHelper', createTopNavHelper(navUi)); + } +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/promises.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/promises.js new file mode 100644 index 0000000000..690bc5489d --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/promises.js @@ -0,0 +1,140 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export function PromiseServiceCreator($q, $timeout) { + function Promise(fn) { + if (typeof this === 'undefined') + throw new Error('Promise constructor must be called with "new"'); + + const defer = $q.defer(); + try { + fn(defer.resolve, defer.reject); + } catch (e) { + defer.reject(e); + } + return defer.promise; + } + + Promise.all = Promise.props = $q.all; + Promise.resolve = function (val) { + const defer = $q.defer(); + defer.resolve(val); + return defer.promise; + }; + Promise.reject = function (reason) { + const defer = $q.defer(); + defer.reject(reason); + return defer.promise; + }; + Promise.cast = $q.when; + Promise.delay = function (ms) { + return $timeout(_.noop, ms); + }; + Promise.method = function (fn) { + return function () { + const args = Array.prototype.slice.call(arguments); + return Promise.try(fn, args, this); + }; + }; + Promise.nodeify = function (promise, cb) { + promise.then(function (val) { + cb(void 0, val); + }, cb); + }; + Promise.map = function (arr, fn) { + return Promise.all( + arr.map(function (i, el, list) { + return Promise.try(fn, [i, el, list]); + }) + ); + }; + Promise.each = function (arr, fn) { + const queue = arr.slice(0); + let i = 0; + return (function next() { + if (!queue.length) return arr; + return Promise.try(fn, [arr.shift(), i++]).then(next); + })(); + }; + Promise.is = function (obj) { + // $q doesn't create instances of any constructor, promises are just objects with a then function + // https://github.com/angular/angular.js/blob/58f5da86645990ef984353418cd1ed83213b111e/src/ng/q.js#L335 + return obj && typeof obj.then === 'function'; + }; + Promise.halt = _.once(function () { + const promise = new Promise(() => {}); + promise.then = _.constant(promise); + promise.catch = _.constant(promise); + return promise; + }); + Promise.try = function (fn, args, ctx) { + if (typeof fn !== 'function') { + return Promise.reject(new TypeError('fn must be a function')); + } + + let value; + + if (Array.isArray(args)) { + try { + value = fn.apply(ctx, args); + } catch (e) { + return Promise.reject(e); + } + } else { + try { + value = fn.call(ctx, args); + } catch (e) { + return Promise.reject(e); + } + } + + return Promise.resolve(value); + }; + Promise.fromNode = function (takesCbFn) { + return new Promise(function (resolve, reject) { + takesCbFn(function (err, ...results) { + if (err) reject(err); + else if (results.length > 1) resolve(results); + else resolve(results[0]); + }); + }); + }; + Promise.race = function (iterable) { + return new Promise((resolve, reject) => { + for (const i of iterable) { + Promise.resolve(i).then(resolve, reject); + } + }); + }; + + return Promise; +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.test.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.test.ts new file mode 100644 index 0000000000..3784988fc8 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.test.ts @@ -0,0 +1,208 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as Rx from 'rxjs'; +import { subscribeWithScope } from './subscribe_with_scope'; + +// eslint-disable-next-line prefer-const +let $rootScope: Scope; + +class Scope { + public $$phase?: string; + public $root = $rootScope; + public $apply = jest.fn((fn: () => void) => fn()); +} + +$rootScope = new Scope(); + +afterEach(() => { + jest.clearAllMocks(); +}); + +it('subscribes to the passed observable, returns subscription', () => { + const $scope = new Scope(); + + const unsubSpy = jest.fn(); + const subSpy = jest.fn(() => unsubSpy); + const observable = new Rx.Observable(subSpy); + + const subscription = subscribeWithScope($scope as any, observable); + expect(subSpy).toHaveBeenCalledTimes(1); + expect(unsubSpy).not.toHaveBeenCalled(); + + subscription.unsubscribe(); + + expect(subSpy).toHaveBeenCalledTimes(1); + expect(unsubSpy).toHaveBeenCalledTimes(1); +}); + +it('calls observer.next() if already in a digest cycle, wraps in $scope.$apply if not', () => { + const subject = new Rx.Subject(); + const nextSpy = jest.fn(); + const $scope = new Scope(); + + subscribeWithScope($scope as any, subject, { next: nextSpy }); + + subject.next(); + expect($scope.$apply).toHaveBeenCalledTimes(1); + expect(nextSpy).toHaveBeenCalledTimes(1); + + jest.clearAllMocks(); + + $rootScope.$$phase = '$digest'; + subject.next(); + expect($scope.$apply).not.toHaveBeenCalled(); + expect(nextSpy).toHaveBeenCalledTimes(1); +}); + +it('reports fatalError if observer.next() throws', () => { + const fatalError = jest.fn(); + const $scope = new Scope(); + subscribeWithScope( + $scope as any, + Rx.of(undefined), + { + next() { + throw new Error('foo bar'); + }, + }, + fatalError + ); + + expect(fatalError.mock.calls).toMatchInlineSnapshot(` +Array [ + Array [ + [Error: foo bar], + ], +] +`); +}); + +it('reports fatal error if observer.error is not defined and observable errors', () => { + const fatalError = jest.fn(); + const $scope = new Scope(); + const error = new Error('foo'); + error.stack = `${error.message}\n---stack trace ---`; + subscribeWithScope($scope as any, Rx.throwError(error), undefined, fatalError); + + expect(fatalError.mock.calls).toMatchInlineSnapshot(` +Array [ + Array [ + [Error: Uncaught error in subscribeWithScope(): foo +---stack trace ---], + ], +] +`); +}); + +it('reports fatal error if observer.error throws', () => { + const fatalError = jest.fn(); + const $scope = new Scope(); + subscribeWithScope( + $scope as any, + Rx.throwError(new Error('foo')), + { + error: () => { + throw new Error('foo'); + }, + }, + fatalError + ); + + expect(fatalError.mock.calls).toMatchInlineSnapshot(` +Array [ + Array [ + [Error: foo], + ], +] +`); +}); + +it('does not report fatal error if observer.error handles the error', () => { + const fatalError = jest.fn(); + const $scope = new Scope(); + subscribeWithScope( + $scope as any, + Rx.throwError(new Error('foo')), + { + error: () => { + // noop, swallow error + }, + }, + fatalError + ); + + expect(fatalError.mock.calls).toEqual([]); +}); + +it('reports fatal error if observer.complete throws', () => { + const fatalError = jest.fn(); + const $scope = new Scope(); + subscribeWithScope( + $scope as any, + Rx.EMPTY, + { + complete: () => { + throw new Error('foo'); + }, + }, + fatalError + ); + + expect(fatalError.mock.calls).toMatchInlineSnapshot(` +Array [ + Array [ + [Error: foo], + ], +] +`); +}); + +it('preserves the context of the observer functions', () => { + const $scope = new Scope(); + const observer = { + next() { + expect(this).toBe(observer); + }, + complete() { + expect(this).toBe(observer); + }, + }; + + subscribeWithScope($scope as any, Rx.of([1, 2, 3]), observer); + + const observer2 = { + error() { + expect(this).toBe(observer); + }, + }; + + subscribeWithScope($scope as any, Rx.throwError(new Error('foo')), observer2); +}); diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.ts new file mode 100644 index 0000000000..f8cb102379 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/subscribe_with_scope.ts @@ -0,0 +1,96 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IScope } from 'angular'; +import * as Rx from 'rxjs'; +import { AngularHttpError } from '../notify/lib'; + +type FatalErrorFn = (error: AngularHttpError | Error | string, location?: string) => void; + +function callInDigest($scope: IScope, fn: () => void, fatalError?: FatalErrorFn) { + try { + // this is terrible, but necessary to synchronously deliver subscription values + // to angular scopes. This is required by some APIs, like the `config` service, + // and beneficial for root level directives where additional digest cycles make + // opensearch dashboards sluggish to load. + // + // If you copy this code elsewhere you better have a good reason :) + if ($scope.$root.$$phase) { + fn(); + } else { + $scope.$apply(() => fn()); + } + } catch (error) { + if (fatalError) { + fatalError(error); + } + } +} + +/** + * Subscribe to an observable at a $scope, ensuring that the digest cycle + * is run for subscriber hooks and routing errors to fatalError if not handled. + */ +export function subscribeWithScope( + $scope: IScope, + observable: Rx.Observable, + observer?: Rx.PartialObserver, + fatalError?: FatalErrorFn +) { + return observable.subscribe({ + next(value) { + if (observer && observer.next) { + callInDigest($scope, () => observer.next!(value), fatalError); + } + }, + error(error) { + callInDigest( + $scope, + () => { + if (observer && observer.error) { + observer.error(error); + } else { + throw new Error( + `Uncaught error in subscribeWithScope(): ${ + error ? error.stack || error.message : error + }` + ); + } + }, + fatalError + ); + }, + complete() { + if (observer && observer.complete) { + callInDigest($scope, () => observer.complete!(), fatalError); + } + }, + }); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/watch_multi.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/watch_multi.js new file mode 100644 index 0000000000..8dfcb0f594 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular/watch_multi.js @@ -0,0 +1,159 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export function watchMultiDecorator($provide) { + $provide.decorator('$rootScope', function ($delegate) { + /** + * Watch multiple expressions with a single callback. Along + * with making code simpler it also merges all of the watcher + * handlers within a single tick. + * + * # expression format + * expressions can be specified in one of the following ways: + * 1. string that evaluates to a value on scope. Creates a regular $watch + * expression. + * 'someScopeValue.prop' === $scope.$watch('someScopeValue.prop', fn); + * + * 2. #1 prefixed with '[]', which uses $watchCollection rather than $watch. + * '[]expr' === $scope.$watchCollection('expr', fn); + * + * 3. #1 prefixed with '=', which uses $watch with objectEquality turned on + * '=expr' === $scope.$watch('expr', fn, true); + * + * 4. a function that will be called, like a normal function water + * + * 5. an object with any of the properties: + * `get`: the getter called on each iteration + * `deep`: a flag to turn on objectEquality in $watch + * `fn`: the watch registration function ($scope.$watch or $scope.$watchCollection) + * + * @param {array[string|function|obj]} expressions - the list of expressions to $watch + * @param {Function} fn - the callback function + * @return {Function} - an unwatch function, just like the return value of $watch + */ + $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { + if (!Array.isArray(expressions)) { + throw new TypeError('expected an array of expressions to watch'); + } + + if (!_.isFunction(fn)) { + throw new TypeError('expected a function that is triggered on each watch'); + } + const $scope = this; + const vals = new Array(expressions.length); + const prev = new Array(expressions.length); + let fire = false; + let init = 0; + const neededInits = expressions.length; + + // first, register all of the multi-watchers + const unwatchers = expressions.map(function (expr, i) { + expr = normalizeExpression($scope, expr); + if (!expr) return; + + return expr.fn.call( + $scope, + expr.get, + function (newVal, oldVal) { + if (newVal === oldVal) { + init += 1; + } + + vals[i] = newVal; + prev[i] = oldVal; + fire = true; + }, + expr.deep + ); + }); + + // then, the watcher that checks to see if any of + // the other watchers triggered this cycle + let flip = false; + unwatchers.push( + $scope.$watch( + function () { + if (init < neededInits) return init; + + if (fire) { + fire = false; + flip = !flip; + } + return flip; + }, + function () { + if (init < neededInits) return false; + + fn(vals.slice(0), prev.slice(0)); + vals.forEach(function (v, i) { + prev[i] = v; + }); + } + ) + ); + + return function () { + unwatchers.forEach((listener) => listener()); + }; + }; + + function normalizeExpression($scope, expr) { + if (!expr) return; + const norm = { + fn: $scope.$watch, + deep: false, + }; + + if (_.isFunction(expr)) return _.assign(norm, { get: expr }); + if (_.isObject(expr)) return _.assign(norm, expr); + if (!_.isString(expr)) return; + + if (expr.substr(0, 2) === '[]') { + return _.assign(norm, { + fn: $scope.$watchCollection, + get: expr.substr(2), + }); + } + + if (expr.charAt(0) === '=') { + return _.assign(norm, { + deep: true, + get: expr.substr(1), + }); + } + + return _.assign(norm, { get: expr }); + } + + return $delegate; + }); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/bind_html/bind_html.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/bind_html/bind_html.js new file mode 100755 index 0000000000..5e6f2edea6 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/bind_html/bind_html.js @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* eslint-disable */ + +import angular from 'angular'; + +export function initBindHtml() { + angular + .module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function() { + return function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/index.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/index.ts new file mode 100644 index 0000000000..63b0431ebb --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/index.ts @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* eslint-disable */ + +import { once } from 'lodash'; +import angular from 'angular'; + +// @ts-ignore +import { initBindHtml } from './bind_html/bind_html'; +// @ts-ignore +import { initBootstrapTooltip } from './tooltip/tooltip'; + +import tooltipPopup from './tooltip/tooltip_popup.html'; + +import tooltipUnsafePopup from './tooltip/tooltip_html_unsafe_popup.html'; + +export const initAngularBootstrap = once(() => { + /* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.12.1 - 2015-02-20 + * License: MIT + */ + angular.module('ui.bootstrap', [ + 'ui.bootstrap.tpls', + 'ui.bootstrap.bindHtml', + 'ui.bootstrap.tooltip', + ]); + + angular.module('ui.bootstrap.tpls', [ + 'template/tooltip/tooltip-html-unsafe-popup.html', + 'template/tooltip/tooltip-popup.html', + ]); + + initBindHtml(); + initBootstrapTooltip(); + + angular.module('template/tooltip/tooltip-html-unsafe-popup.html', []).run([ + '$templateCache', + function($templateCache: any) { + $templateCache.put('template/tooltip/tooltip-html-unsafe-popup.html', tooltipUnsafePopup); + }, + ]); + + angular.module('template/tooltip/tooltip-popup.html', []).run([ + '$templateCache', + function($templateCache: any) { + $templateCache.put('template/tooltip/tooltip-popup.html', tooltipPopup); + }, + ]); +}); diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/position.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/position.js new file mode 100755 index 0000000000..2f322e2b42 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/position.js @@ -0,0 +1,178 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* eslint-disable */ + +import angular from 'angular'; + +export function initBootstrapPosition() { + angular + .module('ui.bootstrap.position', []) + + /** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', [ + '$document', + '$window', + function($document, $window) { + function getStyle(el, cssprop) { + if (el.currentStyle) { + //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static') === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + const parentOffsetEl = function(element) { + const docDomEl = $document[0]; + let offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function(element) { + const elBCR = this.offset(element); + let offsetParentBCR = { top: 0, left: 0 }; + const offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + const boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left, + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function(element) { + const boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: + boundingClientRect.top + + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: + boundingClientRect.left + + ($window.pageXOffset || $document[0].documentElement.scrollLeft), + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function(hostEl, targetEl, positionStr, appendToBody) { + const positionStrParts = positionStr.split('-'); + const pos0 = positionStrParts[0]; + const pos1 = positionStrParts[1] || 'center'; + + let hostElPos; + let targetElWidth; + let targetElHeight; + let targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + const shiftWidth = { + center: function() { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function() { + return hostElPos.left; + }, + right: function() { + return hostElPos.left + hostElPos.width; + }, + }; + + const shiftHeight = { + center: function() { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function() { + return hostElPos.top; + }, + bottom: function() { + return hostElPos.top + hostElPos.height; + }, + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0](), + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth, + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1](), + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1](), + }; + break; + } + + return targetElPos; + }, + }; + }, + ]); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip.js new file mode 100755 index 0000000000..086fa6a7d6 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip.js @@ -0,0 +1,434 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* eslint-disable */ + +import angular from 'angular'; + +import { initBootstrapPosition } from './position'; + +export function initBootstrapTooltip() { + initBootstrapPosition(); + /** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ + angular + .module('ui.bootstrap.tooltip', ['ui.bootstrap.position']) + + /** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ + .provider('$tooltip', function() { + // The default options tooltip and popover. + const defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0, + }; + + // Default hide triggers for each show trigger + const triggerMap = { + mouseenter: 'mouseleave', + click: 'click', + focus: 'blur', + }; + + // The options specified to the provider globally. + const globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function(value) { + angular.extend(globalOptions, value); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers(triggers) { + angular.extend(triggerMap, triggers); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name) { + const regexp = /[A-Z]/g; + const separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ + '$window', + '$compile', + '$timeout', + '$document', + '$position', + '$interpolate', + function($window, $compile, $timeout, $document, $position, $interpolate) { + return function $tooltip(type, prefix, defaultTriggerShow) { + const options = angular.extend({}, defaultOptions, globalOptions); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers(trigger) { + const show = trigger || options.trigger || defaultTriggerShow; + const hide = triggerMap[show] || show; + return { + show: show, + hide: hide, + }; + } + + const directiveName = snake_case(type); + + const startSym = $interpolate.startSymbol(); + const endSym = $interpolate.endSymbol(); + const template = + '
' + + '
'; + + return { + restrict: 'EA', + compile: function(tElem, tAttrs) { + const tooltipLinker = $compile(template); + + return function link(scope, element, attrs) { + let tooltip; + let tooltipLinkedScope; + let transitionTimeout; + let popupTimeout; + let appendToBody = angular.isDefined(options.appendToBody) + ? options.appendToBody + : false; + let triggers = getTriggers(undefined); + const hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); + let ttScope = scope.$new(true); + + const positionTooltip = function() { + const ttPosition = $position.positionElements( + element, + tooltip, + ttScope.placement, + appendToBody + ); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css(ttPosition); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + ttScope.isOpen = false; + + function toggleTooltipBind() { + if (!ttScope.isOpen) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { + return; + } + + prepareTooltip(); + + if (ttScope.popupDelay) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout(show, ttScope.popupDelay, false); + popupTimeout + .then(reposition => reposition()) + .catch(error => { + // if the timeout is canceled then the string `canceled` is thrown. To prevent + // this from triggering an 'unhandled promise rejection' in angular 1.5+ the + // $timeout service explicitly tells $q that the promise it generated is "handled" + // but that does not include down chain promises like the one created by calling + // `popupTimeout.then()`. Because of this we need to ignore the "canceled" string + // and only propagate real errors + if (error !== 'canceled') { + throw error; + } + }); + } + } else { + show()(); + } + } + + function hideTooltipBind() { + scope.$evalAsync(function() { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if (transitionTimeout) { + $timeout.cancel(transitionTimeout); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if (!ttScope.content) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + ttScope.$digest(); + + positionTooltip(); + + // And show the tooltip. + ttScope.isOpen = true; + ttScope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + ttScope.isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel(popupTimeout); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if (ttScope.animation) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltipLinkedScope = ttScope.$new(); + tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { + if (appendToBody) { + $document.find('body').append(tooltip); + } else { + element.after(tooltip); + } + }); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + if (tooltipLinkedScope) { + tooltipLinkedScope.$destroy(); + tooltipLinkedScope = null; + } + } + + function prepareTooltip() { + prepPlacement(); + prepPopupDelay(); + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe(type, function(val) { + ttScope.content = val; + + if (!val && ttScope.isOpen) { + hide(); + } + }); + + attrs.$observe(prefix + 'Title', function(val) { + ttScope.title = val; + }); + + function prepPlacement() { + const val = attrs[prefix + 'Placement']; + ttScope.placement = angular.isDefined(val) ? val : options.placement; + } + + function prepPopupDelay() { + const val = attrs[prefix + 'PopupDelay']; + const delay = parseInt(val, 10); + ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; + } + + const unregisterTriggers = function() { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + function prepTriggers() { + const val = attrs[prefix + 'Trigger']; + unregisterTriggers(); + + triggers = getTriggers(val); + + if (triggers.show === triggers.hide) { + element.bind(triggers.show, toggleTooltipBind); + } else { + element.bind(triggers.show, showTooltipBind); + element.bind(triggers.hide, hideTooltipBind); + } + } + + prepTriggers(); + + const animation = scope.$eval(attrs[prefix + 'Animation']); + ttScope.animation = angular.isDefined(animation) + ? !!animation + : options.animation; + + const appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']); + appendToBody = angular.isDefined(appendToBodyVal) + ? appendToBodyVal + : appendToBody; + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if (appendToBody) { + scope.$on( + '$locationChangeSuccess', + function closeTooltipOnLocationChangeSuccess() { + if (ttScope.isOpen) { + hide(); + } + } + ); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel(transitionTimeout); + $timeout.cancel(popupTimeout); + unregisterTriggers(); + removeTooltip(); + ttScope = null; + }); + }; + }, + }; + }; + }, + ]; + }) + + .directive('tooltip', [ + '$tooltip', + function($tooltip) { + return $tooltip('tooltip', 'tooltip', 'mouseenter'); + }, + ]) + + .directive('tooltipPopup', function() { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html', + }; + }) + + .directive('tooltipHtmlUnsafe', [ + '$tooltip', + function($tooltip) { + return $tooltip('tooltipHtmlUnsafe', 'tooltip', 'mouseenter'); + }, + ]) + + .directive('tooltipHtmlUnsafePopup', function() { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html', + }; + }); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_html_unsafe_popup.html b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_html_unsafe_popup.html new file mode 100644 index 0000000000..b48bf70498 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_html_unsafe_popup.html @@ -0,0 +1,4 @@ +
+
+
+
\ No newline at end of file diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_popup.html b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_popup.html new file mode 100644 index 0000000000..eed4ca7d93 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/angular_bootstrap/tooltip/tooltip_popup.html @@ -0,0 +1,4 @@ +
+
+
+
\ No newline at end of file diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/index.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/index.ts new file mode 100644 index 0000000000..36fd9a993c --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/index.ts @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './angular'; +export * from './angular_bootstrap'; +export * from './notify'; +export * from './utils'; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/index.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/index.ts new file mode 100644 index 0000000000..934ca937e1 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/index.ts @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './toasts'; +export * from './lib'; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/add_fatal_error.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/add_fatal_error.ts new file mode 100644 index 0000000000..beb6f81e3e --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/add_fatal_error.ts @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FatalErrorsSetup } from '../../../../../core/public'; +import { + AngularHttpError, + formatAngularHttpError, + isAngularHttpError, +} from './format_angular_http_error'; + +export function addFatalError( + fatalErrors: FatalErrorsSetup, + error: AngularHttpError | Error | string, + location?: string +) { + // add support for angular http errors to newPlatformFatalErrors + if (isAngularHttpError(error)) { + error = formatAngularHttpError(error); + } + + fatalErrors.add(error, location); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_angular_http_error.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_angular_http_error.ts new file mode 100644 index 0000000000..68b3701814 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_angular_http_error.ts @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@osd/i18n'; +import { IHttpResponse } from 'angular'; + +export type AngularHttpError = IHttpResponse<{ message: string }>; + +export function isAngularHttpError(error: any): error is AngularHttpError { + return ( + error && + typeof error.status === 'number' && + typeof error.statusText === 'string' && + error.data && + typeof error.data.message === 'string' + ); +} + +export function formatAngularHttpError(error: AngularHttpError) { + // is an Angular $http "error object" + if (error.status === -1) { + // status = -1 indicates that the request was failed to reach the server + return i18n.translate( + 'opensearch_dashboards_legacy.notify.fatalError.unavailableServerErrorMessage', + { + defaultMessage: + 'An HTTP request has failed to connect. ' + + 'Please check if the OpenSearch Dashboards server is running and that your browser has a working connection, ' + + 'or contact your system administrator.', + } + ); + } + + return i18n.translate('opensearch_dashboards_legacy.notify.fatalError.errorStatusMessage', { + defaultMessage: 'Error {errStatus} {errStatusText}: {errMessage}', + values: { + errStatus: error.status, + errStatusText: error.statusText, + errMessage: error.data.message, + }, + }); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.test.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.test.js new file mode 100644 index 0000000000..aee1e140d3 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.test.js @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { formatMsg } from './format_msg'; +import expect from '@osd/expect'; + +describe('formatMsg', () => { + test('should prepend the second argument to result', () => { + const actual = formatMsg('error message', 'unit_test'); + + expect(actual).to.equal('unit_test: error message'); + }); + + test('should handle a simple string', () => { + const actual = formatMsg('error message'); + + expect(actual).to.equal('error message'); + }); + + test('should handle a simple Error object', () => { + const err = new Error('error message'); + const actual = formatMsg(err); + + expect(actual).to.equal('error message'); + }); + + test('should handle a simple Angular $http error object', () => { + const err = { + data: { + statusCode: 403, + error: 'Forbidden', + message: + '[security_exception] action [indices:data/read/msearch] is unauthorized for user [user]', + }, + status: 403, + config: {}, + statusText: 'Forbidden', + }; + const actual = formatMsg(err); + + expect(actual).to.equal( + 'Error 403 Forbidden: [security_exception] action [indices:data/read/msearch] is unauthorized for user [user]' + ); + }); + + test('should handle an extended opensearch error', () => { + const err = { + resp: { + error: { + root_cause: [ + { + reason: 'I am the detailed message', + }, + ], + }, + }, + }; + + const actual = formatMsg(err); + + expect(actual).to.equal('I am the detailed message'); + }); +}); diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.ts new file mode 100644 index 0000000000..67a4771219 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_msg.ts @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { i18n } from '@osd/i18n'; +import { formatOpenSearchMsg } from './format_opensearch_msg'; +const has = _.has; + +/** + * Formats the error message from an error object, extended opensearch + * object or simple string; prepends optional second parameter to the message + * @param {Error|String} err + * @param {String} source - Prefix for message indicating source (optional) + * @returns {string} + */ +export function formatMsg(err: Record | string, source: string = '') { + let message = ''; + if (source) { + message += source + ': '; + } + + const opensearchMsg = formatOpenSearchMsg(err); + + if (typeof err === 'string') { + message += err; + } else if (opensearchMsg) { + message += opensearchMsg; + } else if (err instanceof Error) { + message += formatMsg.describeError(err); + } else if (has(err, 'status') && has(err, 'data')) { + // is an Angular $http "error object" + if (err.status === -1) { + // status = -1 indicates that the request was failed to reach the server + message += i18n.translate( + 'opensearch_dashboards_legacy.notify.toaster.unavailableServerErrorMessage', + { + defaultMessage: + 'An HTTP request has failed to connect. ' + + 'Please check if the OpenSearch Dashboards server is running and that your browser has a working connection, ' + + 'or contact your system administrator.', + } + ); + } else { + message += i18n.translate('opensearch_dashboards_legacy.notify.toaster.errorStatusMessage', { + defaultMessage: 'Error {errStatus} {errStatusText}: {errMessage}', + values: { + errStatus: err.status, + errStatusText: err.statusText, + errMessage: err.data.message, + }, + }); + } + } + + return message; +} + +formatMsg.describeError = function (err: Record) { + if (!err) return undefined; + if (err.shortMessage) return err.shortMessage; + if (err.body && err.body.message) return err.body.message; + if (err.message) return err.message; + return '' + err; +}; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.test.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.test.js new file mode 100644 index 0000000000..bd20b36f35 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.test.js @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { formatOpenSearchMsg } from './format_opensearch_msg'; +import expect from '@osd/expect'; + +describe('formatOpenSearchMsg', () => { + test('should return undefined if passed a basic error', () => { + const err = new Error('This is a normal error'); + + const actual = formatOpenSearchMsg(err); + + expect(actual).to.be(undefined); + }); + + test('should return undefined if passed a string', () => { + const err = 'This is a error string'; + + const actual = formatOpenSearchMsg(err); + + expect(actual).to.be(undefined); + }); + + test('should return the root_cause if passed an extended opensearch', () => { + const err = new Error('This is an opensearch error'); + err.resp = { + error: { + root_cause: [ + { + reason: 'I am the detailed message', + }, + ], + }, + }; + + const actual = formatOpenSearchMsg(err); + + expect(actual).to.equal('I am the detailed message'); + }); + + test('should combine the reason messages if more than one is returned.', () => { + const err = new Error('This is an opensearch error'); + err.resp = { + error: { + root_cause: [ + { + reason: 'I am the detailed message 1', + }, + { + reason: 'I am the detailed message 2', + }, + ], + }, + }; + + const actual = formatOpenSearchMsg(err); + + expect(actual).to.equal('I am the detailed message 1\nI am the detailed message 2'); + }); +}); diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.ts new file mode 100644 index 0000000000..b253b949b7 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_opensearch_msg.ts @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +const getRootCause = (err: Record | string) => _.get(err, 'resp.error.root_cause'); + +/** + * Utilize the extended error information returned from opensearch + * @param {Error|String} err + * @returns {string} + */ +export const formatOpenSearchMsg = (err: Record | string) => { + const rootCause = getRootCause(err); + + if (!Array.isArray(rootCause)) { + return; + } + + return rootCause.map((cause: Record) => cause.reason).join('\n'); +}; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_stack.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_stack.ts new file mode 100644 index 0000000000..d571ebc69e --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/format_stack.ts @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@osd/i18n'; + +// browsers format Error.stack differently; always include message +export function formatStack(err: Record) { + if (err.stack && err.stack.indexOf(err.message) === -1) { + return i18n.translate('opensearch_dashboards_legacy.notify.toaster.errorMessage', { + defaultMessage: `Error: {errorMessage} + {errorStack}`, + values: { + errorMessage: err.message, + errorStack: err.stack, + }, + }); + } + return err.stack; +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/index.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/index.ts new file mode 100644 index 0000000000..22a8631dfe --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/lib/index.ts @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { formatOpenSearchMsg } from './format_opensearch_msg'; +export { formatMsg } from './format_msg'; +export { formatStack } from './format_stack'; +export { + isAngularHttpError, + formatAngularHttpError, + AngularHttpError, +} from './format_angular_http_error'; +export { addFatalError } from './add_fatal_error'; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md new file mode 100644 index 0000000000..de6a51f392 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md @@ -0,0 +1,100 @@ +# Toast notifications + +Use this service to surface toasts in the bottom-right corner of the screen. After a brief delay, they'll disappear. They're useful for notifying the user of state changes. See [the EUI docs](https://elastic.github.io/eui/) for more information on toasts and their role within the UI. + +## Importing the module + +```js +import { toastNotifications } from 'ui/notify'; +``` + +## Interface + +### Adding toasts + +For convenience, there are several methods which predefine the appearance of different types of toasts. Use these methods so that the same types of toasts look similar to the user. + +#### Default + +Neutral toast. Tell the user a change in state has occurred, which is not necessarily good or bad. + +```js +toastNotifications.add('Copied to clipboard'); +``` + +#### Success + +Let the user know that an action was successful, such as saving or deleting an object. + +```js +toastNotifications.addSuccess('Your document was saved'); +``` + +#### Warning + +If something OK or good happened, but perhaps wasn't perfect, show a warning toast. + +```js +toastNotifications.addWarning('Your document was saved, but not its edit history'); +``` + +#### Danger + +When the user initiated an action but the action failed, show them a danger toast. + +```js +toastNotifications.addDanger('An error caused your document to be lost'); +``` + +### Removing a toast + +Toasts will automatically be dismissed after a brief delay, but if for some reason you want to dismiss a toast, you can use the returned toast from one of the `add` methods and then pass it to `remove`. + +```js +const toast = toastNotifications.add('Your document was saved'); +toastNotifications.remove(toast); +``` + +### Configuration options + +If you want to configure the toast further you can provide an object instead of a string. The properties of this object correspond to the `propTypes` accepted by the `EuiToast` component. Refer to [the EUI docs](https://elastic.github.io/eui/) for info on these `propTypes`. + +```js +toastNotifications.add({ + title: 'Your document was saved', + text: 'Only you have access to this document', + color: 'success', + iconType: 'check', + 'data-test-subj': 'saveDocumentSuccess', +}); +``` + +Because the underlying components are React, you can use JSX to pass in React elements to the `text` prop. This gives you total flexibility over the content displayed within the toast. + +```js +toastNotifications.add({ + title: 'Your document was saved', + text: ( +
+

+ Only you have access to this document. Edit permissions. +

+ + +
+ ), +}); +``` + +## Use in functional tests + +Functional tests are commonly used to verify that a user action yielded a successful outcome. If you surface a toast to notify the user of this successful outcome, you can place a `data-test-subj` attribute on the toast and use it to check if the toast exists inside of your functional test. This acts as a proxy for verifying the successful outcome. + +```js +toastNotifications.addSuccess({ + title: 'Your document was saved', + 'data-test-subj': 'saveDocumentSuccess', +}); +``` diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/index.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/index.ts new file mode 100644 index 0000000000..71115523dc --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/index.ts @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ToastNotifications } from './toast_notifications'; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/toast_notifications.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/toast_notifications.ts new file mode 100644 index 0000000000..8860c089c6 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/notify/toasts/toast_notifications.ts @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + NotificationsSetup, + Toast, + ToastInput, + ErrorToastOptions, +} from 'opensearch-dashboards/public'; + +export class ToastNotifications { + public list: Toast[] = []; + + private onChangeCallback?: () => void; + + constructor(private readonly toasts: NotificationsSetup['toasts']) { + toasts.get$().subscribe((list) => { + this.list = list; + + if (this.onChangeCallback) { + this.onChangeCallback(); + } + }); + } + + public onChange = (callback: () => void) => { + this.onChangeCallback = callback; + }; + + public add = (toastOrTitle: ToastInput) => this.toasts.add(toastOrTitle); + public remove = (toast: Toast) => this.toasts.remove(toast); + public addSuccess = (toastOrTitle: ToastInput) => this.toasts.addSuccess(toastOrTitle); + public addWarning = (toastOrTitle: ToastInput) => this.toasts.addWarning(toastOrTitle); + public addDanger = (toastOrTitle: ToastInput) => this.toasts.addDanger(toastOrTitle); + public addError = (error: Error, options: ErrorToastOptions) => + this.toasts.addError(error, options); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/index.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/index.ts new file mode 100644 index 0000000000..6313548a1b --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/index.ts @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './system_api'; +// @ts-ignore +export { OsdAccessibleClickProvider } from './osd_accessible_click'; +// @ts-ignore +export { PrivateProvider, IPrivate } from './private'; +// @ts-ignore +export { registerListenEventListener } from './register_listen_event_listener'; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/inject_header_style.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/inject_header_style.ts new file mode 100644 index 0000000000..3ff447aeb9 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/inject_header_style.ts @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IUiSettingsClient } from 'opensearch-dashboards/public'; + +export function buildCSS(maxHeight = 0, truncateGradientHeight = 15) { + return ` +.truncate-by-height { + max-height: ${maxHeight > 0 ? `${maxHeight}px !important` : 'none'}; + display: inline-block; +} +.truncate-by-height:before { + top: ${maxHeight > 0 ? maxHeight - truncateGradientHeight : truncateGradientHeight * -1}px; +} +`; +} + +export function injectHeaderStyle(uiSettings: IUiSettingsClient) { + const style = document.createElement('style'); + style.setAttribute('id', 'style-compile'); + document.getElementsByTagName('head')[0].appendChild(style); + + uiSettings.get$('truncate:maxHeight').subscribe((value: number) => { + // eslint-disable-next-line no-unsanitized/property + style.innerHTML = buildCSS(value); + }); +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/osd_accessible_click.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/osd_accessible_click.js new file mode 100644 index 0000000000..6c49ff8de4 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/osd_accessible_click.js @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { accessibleClickKeys, keys } from '@elastic/eui'; + +export function OsdAccessibleClickProvider() { + return { + restrict: 'A', + controller: ($element) => { + $element.on('keydown', (e) => { + // Prevent a scroll from occurring if the user has hit space. + if (e.key === keys.SPACE) { + e.preventDefault(); + } + }); + }, + link: (scope, element, attrs) => { + // The whole point of this directive is to hack in functionality that native buttons provide + // by default. + const elementType = element.prop('tagName'); + + if (elementType === 'BUTTON') { + throw new Error(`osdAccessibleClick doesn't need to be used on a button.`); + } + + if (elementType === 'A' && attrs.href !== undefined) { + throw new Error( + `osdAccessibleClick doesn't need to be used on a link if it has a href attribute.` + ); + } + + // We're emulating a click action, so we should already have a regular click handler defined. + if (!attrs.ngClick) { + throw new Error('osdAccessibleClick requires ng-click to be defined on its element.'); + } + + // If the developer hasn't already specified attributes required for accessibility, add them. + if (attrs.tabindex === undefined) { + element.attr('tabindex', '0'); + } + + if (attrs.role === undefined) { + element.attr('role', 'button'); + } + + element.on('keyup', (e) => { + // Support keyboard accessibility by emulating mouse click on ENTER or SPACE keypress. + if (accessibleClickKeys[e.key]) { + // Delegate to the click handler on the element (assumed to be ng-click). + element.click(); + } + }); + }, + }; +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.d.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.d.ts new file mode 100644 index 0000000000..fe264fc193 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.d.ts @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type IPrivate = (provider: (...injectable: any[]) => T) => T; diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.js new file mode 100644 index 0000000000..1a3a0a5965 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/private.js @@ -0,0 +1,214 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * # `Private()` + * Private module loader, used to merge angular and require js dependency styles + * by allowing a require.js module to export a single provider function that will + * create a value used within an angular application. This provider can declare + * angular dependencies by listing them as arguments, and can be require additional + * Private modules. + * + * ## Define a private module provider: + * ```js + * export default function PingProvider($http) { + * this.ping = function () { + * return $http.head('/health-check'); + * }; + * }; + * ``` + * + * ## Require a private module: + * ```js + * export default function ServerHealthProvider(Private, Promise) { + * let ping = Private(require('ui/ping')); + * return { + * check: Promise.method(function () { + * let attempts = 0; + * return (function attempt() { + * attempts += 1; + * return ping.ping() + * .catch(function (err) { + * if (attempts < 3) return attempt(); + * }) + * }()) + * .then(function () { + * return true; + * }) + * .catch(function () { + * return false; + * }); + * }) + * } + * }; + * ``` + * + * # `Private.stub(provider, newInstance)` + * `Private.stub()` replaces the instance of a module with another value. This is all we have needed until now. + * + * ```js + * beforeEach(inject(function ($injector, Private) { + * Private.stub( + * // since this module just exports a function, we need to change + * // what Private returns in order to modify it's behavior + * require('ui/agg_response/hierarchical/_build_split'), + * sinon.stub().returns(fakeSplit) + * ); + * })); + * ``` + * + * # `Private.swap(oldProvider, newProvider)` + * This new method does an 1-for-1 swap of module providers, unlike `stub()` which replaces a modules instance. + * Pass the module you want to swap out, and the one it should be replaced with, then profit. + * + * Note: even though this example shows `swap()` being called in a config + * function, it can be called from anywhere. It is particularly useful + * in this scenario though. + * + * ```js + * beforeEach(module('opensearchDashboards', function (PrivateProvider) { + * PrivateProvider.swap( + * function StubbedRedirectProvider($decorate) { + * // $decorate is a function that will instantiate the original module when called + * return sinon.spy($decorate()); + * } + * ); + * })); + * ``` + * + * @param {[type]} prov [description] + */ +import _ from 'lodash'; + +const nextId = _.partial(_.uniqueId, 'privateProvider#'); + +function name(fn) { + return fn.name || fn.toString().split('\n').shift(); +} + +export function PrivateProvider() { + const provider = this; + + // one cache/swaps per Provider + const cache = {}; + const swaps = {}; + + // return the uniq id for this function + function identify(fn) { + if (typeof fn !== 'function') { + throw new TypeError('Expected private module "' + fn + '" to be a function'); + } + + if (fn.$$id) return fn.$$id; + else return (fn.$$id = nextId()); + } + + provider.stub = function (fn, instance) { + cache[identify(fn)] = instance; + return instance; + }; + + provider.swap = function (fn, prov) { + const id = identify(fn); + swaps[id] = prov; + }; + + provider.$get = [ + '$injector', + function PrivateFactory($injector) { + // prevent circular deps by tracking where we came from + const privPath = []; + const pathToString = function () { + return privPath.map(name).join(' -> '); + }; + + // call a private provider and return the instance it creates + function instantiate(prov, locals) { + if (~privPath.indexOf(prov)) { + throw new Error( + 'Circular reference to "' + + name(prov) + + '"' + + ' found while resolving private deps: ' + + pathToString() + ); + } + + privPath.push(prov); + + const context = {}; + let instance = $injector.invoke(prov, context, locals); + if (!_.isObject(instance)) instance = context; + + privPath.pop(); + return instance; + } + + // retrieve an instance from cache or create and store on + function get(id, prov, $delegateId, $delegateProv) { + if (cache[id]) return cache[id]; + + let instance; + + if ($delegateId != null && $delegateProv != null) { + instance = instantiate(prov, { + $decorate: _.partial(get, $delegateId, $delegateProv), + }); + } else { + instance = instantiate(prov); + } + + return (cache[id] = instance); + } + + // main api, get the appropriate instance for a provider + function Private(prov) { + let id = identify(prov); + let $delegateId; + let $delegateProv; + + if (swaps[id]) { + $delegateId = id; + $delegateProv = prov; + + prov = swaps[$delegateId]; + id = identify(prov); + } + + return get(id, prov, $delegateId, $delegateProv); + } + + Private.stub = provider.stub; + Private.swap = provider.swap; + + return Private; + }, + ]; +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/register_listen_event_listener.js b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/register_listen_event_listener.js new file mode 100644 index 0000000000..19652d94cf --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/register_listen_event_listener.js @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function registerListenEventListener($rootScope) { + /** + * Helper that registers an event listener, and removes that listener when + * the $scope is destroyed. + * + * @param {EventEmitter} emitter - the event emitter to listen to + * @param {string} eventName - the event name + * @param {Function} handler - the event handler + * @return {undefined} + */ + $rootScope.constructor.prototype.$listen = function (emitter, eventName, handler) { + emitter.on(eventName, handler); + this.$on('$destroy', function () { + emitter.off(eventName, handler); + }); + }; +} diff --git a/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/system_api.ts b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/system_api.ts new file mode 100644 index 0000000000..2675bbc084 --- /dev/null +++ b/plugins/main/public/kibana-integrations/plugins/opensearch_dashboards_legacy/public/utils/system_api.ts @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRequestConfig } from 'angular'; + +const SYSTEM_REQUEST_HEADER_NAME = 'osd-system-request'; +const LEGACY_SYSTEM_API_HEADER_NAME = 'osd-system-api'; + +/** + * Adds a custom header designating request as system API + * @param originalHeaders Object representing set of headers + * @return Object representing set of headers, with system API header added in + */ +export function addSystemApiHeader(originalHeaders: Record) { + const systemApiHeaders = { + [SYSTEM_REQUEST_HEADER_NAME]: true, + }; + return { + ...originalHeaders, + ...systemApiHeaders, + }; +} + +/** + * Returns true if request is a system API request; false otherwise + * + * @param request Object Request object created by $http service + * @return true if request is a system API request; false otherwise + */ +export function isSystemApiRequest(request: IRequestConfig) { + const { headers } = request; + return ( + headers && (!!headers[SYSTEM_REQUEST_HEADER_NAME] || !!headers[LEGACY_SYSTEM_API_HEADER_NAME]) + ); +} diff --git a/plugins/main/yarn.lock b/plugins/main/yarn.lock index 915a66d1d9..b04bca1416 100644 --- a/plugins/main/yarn.lock +++ b/plugins/main/yarn.lock @@ -654,11 +654,36 @@ angular-animate@1.8.3: resolved "https://registry.yarnpkg.com/angular-animate/-/angular-animate-1.8.3.tgz#f88db37325de256f9144d1242ce3158134a9d72a" integrity sha512-/LtTKvy5sD6MZbV0v+nHgOIpnFF0mrUp+j5WIxVprVhcrJriYpuCZf4S7Owj1o76De/J0eRzANUozNJ6hVepnQ== +angular-aria@^1.8.0: + version "1.8.3" + resolved "https://registry.yarnpkg.com/angular-aria/-/angular-aria-1.8.3.tgz#b387ebca9569eb557855abb283a09c2d0457e779" + integrity sha512-qTXclmTW/KGw5JNKKQPcCKKq6hCBZ39jYINmLgMsjUHBAoxULaMRRTaRj/L2VTOjKvK5f9enkx+EUqRqzXDSFQ== + angular-material@1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/angular-material/-/angular-material-1.2.5.tgz#6fb3fbf3622d443e4449aaf237d692ad04623a23" integrity sha512-bTTDV0vszpfms1tAMzhLntxBiNMCk/I3Mx/vhbtfhijJILODjpDBfWah0nvWrniFIcxMLcsb1tcPri13hZEaew== +angular-mocks@^1.8.2: + version "1.8.3" + resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.8.3.tgz#c0dd05e5c3fc014e07af6289b23f0e817d7a4724" + integrity sha512-vqsT6zwu80cZ8RY7qRQBZuy6Fq5X7/N5hkV9LzNT0c8b546rw4ErGK6muW1u2JnDKYa7+jJuaGM702bWir4HGw== + +angular-route@^1.8.0: + version "1.8.3" + resolved "https://registry.yarnpkg.com/angular-route/-/angular-route-1.8.3.tgz#f24a700ebd462454ca83a8b765df55c87a4edde1" + integrity sha512-kpIcRmDR2+o1FxDVVYy8Rvfab86/7LDbOgTRb9T+X9ewPQiBRuDEnZtM3oJYBiQLvAXDYTJXHV48n/bGE9Mv2g== + +angular-sanitize@^1.8.0: + version "1.8.3" + resolved "https://registry.yarnpkg.com/angular-sanitize/-/angular-sanitize-1.8.3.tgz#51378e990e78c7ecc1fb31cd68655aff690a3bf1" + integrity sha512-2rxdqzlUVafUeWOwvY/FtyWk1pFTyCtzreeiTytG9m4smpuAEKaIJAjYeVwWsoV+nlTOcgpwV4W1OCmR+BQbUg== + +angular@^1.8.2: + version "1.8.3" + resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.3.tgz#851ad75d5163c105a7e329555ef70c90aa706894" + integrity sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw== + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -2586,6 +2611,11 @@ next-tick@^1.1.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== +ngreact@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/ngreact/-/ngreact-0.5.2.tgz#d48180b578b186ad70861a3de9ba508b3f22b2ae" + integrity sha512-FCQGtTkDrnI3ywhvK9wUf7C6SYfqKDdRW+cPvy358GFe3AnA4rfvWisDVUQyf5YwNr439ito9xUuuEv80QXhSQ== + node-abi@^3.3.0: version "3.45.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.45.0.tgz#f568f163a3bfca5aacfce1fbeee1fa2cc98441f5" diff --git a/plugins/wazuh-check-updates/opensearch_dashboards.json b/plugins/wazuh-check-updates/opensearch_dashboards.json index 112f05fa5a..bf69812edc 100644 --- a/plugins/wazuh-check-updates/opensearch_dashboards.json +++ b/plugins/wazuh-check-updates/opensearch_dashboards.json @@ -1,6 +1,6 @@ { "id": "wazuhCheckUpdates", - "version": "4.8.1-00", + "version": "4.9.0-00", "opensearchDashboardsVersion": "opensearchDashboards", "server": true, "ui": true, @@ -13,4 +13,4 @@ "optionalPlugins": [ "securityDashboards" ] -} \ No newline at end of file +} diff --git a/plugins/wazuh-check-updates/package.json b/plugins/wazuh-check-updates/package.json index 106451ed81..1f3a34506a 100644 --- a/plugins/wazuh-check-updates/package.json +++ b/plugins/wazuh-check-updates/package.json @@ -1,9 +1,9 @@ { "name": "wazuh-check-updates", - "version": "4.8.1", + "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.10.0" + "version": "2.11.0" }, "description": "Wazuh Check Updates", "private": true, @@ -29,4 +29,4 @@ "@types/md5": "^2.3.2", "@types/node-cron": "^3.0.8" } -} \ No newline at end of file +} diff --git a/plugins/wazuh-check-updates/yarn.lock b/plugins/wazuh-check-updates/yarn.lock index 77ac1ea0cc..79110b0f40 100644 --- a/plugins/wazuh-check-updates/yarn.lock +++ b/plugins/wazuh-check-updates/yarn.lock @@ -50,10 +50,10 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -axios@^1.5.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.1.tgz#76550d644bf0a2d469a01f9244db6753208397d7" - integrity sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g== +axios@^1.6.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" diff --git a/plugins/wazuh-core/opensearch_dashboards.json b/plugins/wazuh-core/opensearch_dashboards.json index a68490fc52..47ff8cfd52 100644 --- a/plugins/wazuh-core/opensearch_dashboards.json +++ b/plugins/wazuh-core/opensearch_dashboards.json @@ -1,6 +1,6 @@ { "id": "wazuhCore", - "version": "4.8.1-00", + "version": "4.9.0-00", "opensearchDashboardsVersion": "opensearchDashboards", "server": true, "ui": true, @@ -11,4 +11,4 @@ "optionalPlugins": [ "securityDashboards" ] -} \ No newline at end of file +} diff --git a/plugins/wazuh-core/package.json b/plugins/wazuh-core/package.json index e0a811f4f9..82522f6cba 100644 --- a/plugins/wazuh-core/package.json +++ b/plugins/wazuh-core/package.json @@ -1,9 +1,9 @@ { "name": "wazuh-core", - "version": "4.8.1", + "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.10.0" + "version": "2.11.0" }, "description": "Wazuh Core", "private": true, @@ -30,4 +30,4 @@ "@types/": "testing-library/user-event", "@types/md5": "^2.3.2" } -} \ No newline at end of file +} diff --git a/plugins/wazuh-core/yarn.lock b/plugins/wazuh-core/yarn.lock index a28ac899ed..10107cf612 100644 --- a/plugins/wazuh-core/yarn.lock +++ b/plugins/wazuh-core/yarn.lock @@ -45,10 +45,10 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -axios@^1.5.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.1.tgz#76550d644bf0a2d469a01f9244db6753208397d7" - integrity sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g== +axios@^1.6.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" From 10f26fb72399d38f663cb54799f2fc723dc71cd7 Mon Sep 17 00:00:00 2001 From: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:57:05 +0100 Subject: [PATCH 009/136] Update Build manual Github action (#6195) --- .github/workflows/dev-environment.yml | 8 +++++++- .github/workflows/manual-build.yml | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev-environment.yml b/.github/workflows/dev-environment.yml index b20ce7d91e..92aedc3fee 100644 --- a/.github/workflows/dev-environment.yml +++ b/.github/workflows/dev-environment.yml @@ -70,6 +70,7 @@ jobs: - name: Step 01 - Download the plugin's source code uses: actions/checkout@v3 with: + repository: wazuh/wazuh-dashboard-plugins ref: ${{ inputs.reference }} path: wazuh @@ -107,11 +108,16 @@ jobs: cd /home/node/kbn/plugins/${{ matrix.plugins.container_path }} && yarn && ${{ inputs.command }}; ' + - name: Get the plugin version + run: | + echo "version=$(jq -r '.version' $(pwd)/wazuh/plugins/main/package.json)" >> $GITHUB_ENV + echo "revision=$(jq -r '.revision' $(pwd)/wazuh/plugins/main/package.json)" >> $GITHUB_ENV + - name: Step 04 - Upload artifact to GitHub if: ${{ inputs.artifact_name && inputs.artifact_path }} uses: actions/upload-artifact@v3 with: - name: ${{ inputs.artifact_name }} + name: ${{ inputs.artifact_name }}_${{ env.version }}-${{ env.revision }}_${{ inputs.reference }}.zip path: ${{ matrix.plugins.path }}/${{ inputs.artifact_path }} if-no-files-found: 'error' diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 32b03b0d14..859c9e105a 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -6,6 +6,13 @@ name: Manual build on: + workflow_call: + inputs: + reference: + required: true + type: string + default: master + description: Source code reference (branch, tag or commit SHA) workflow_dispatch: inputs: reference: @@ -20,7 +27,7 @@ jobs: name: Build app package uses: ./.github/workflows/dev-environment.yml with: - reference: ${{ github.event.inputs.reference }} + reference: ${{ inputs.reference }} command: 'yarn build' - artifact_name: 'wazuh-package' + artifact_name: 'wazuh-dashboard-plugins' secrets: inherit From 4b967e233d03f04273a033e7ae8510a032d7428e Mon Sep 17 00:00:00 2001 From: Maximiliano Ibarra <6089438+Machi3mfl@users.noreply.github.com> Date: Thu, 14 Dec 2023 15:42:07 -0300 Subject: [PATCH 010/136] Add new discover component and children components (#6205) * Add search_bar hook * Add doc_viewer component and hook * Add data_grid hook * Add discover component * Fixed use_search_bar unit tests * Add index file in doc_viewer folder * Resolve review requested changes * Solve requested changes * Solve requested change * Relocated data_grid and search_bar folders * Replace naming convention to kebab-case * Update CHANGELOG --- CHANGELOG.md | 1 + .../common/data-grid/data-grid-service.ts | 163 ++++++++++++ .../components/common/data-grid/index.ts | 2 + .../common/data-grid/use-data-grid.ts | 103 ++++++++ .../common/doc-viewer/doc-viewer.tsx | 150 +++++++++++ .../components/common/doc-viewer/index.ts | 2 + .../common/doc-viewer/use-doc-viewer.ts | 28 ++ .../components/common/search-bar/index.ts | 2 + .../common/search-bar/search-bar-service.ts | 56 ++++ .../common/search-bar/use-search-bar.test.ts | 219 ++++++++++++++++ .../common/search-bar/use-search-bar.ts | 173 +++++++++++++ .../common/wazuh-discover/config/chart.ts | 130 ++++++++++ .../common/wazuh-discover/wz-discover.tsx | 245 ++++++++++++++++++ 13 files changed, 1274 insertions(+) create mode 100644 plugins/main/public/components/common/data-grid/data-grid-service.ts create mode 100644 plugins/main/public/components/common/data-grid/index.ts create mode 100644 plugins/main/public/components/common/data-grid/use-data-grid.ts create mode 100644 plugins/main/public/components/common/doc-viewer/doc-viewer.tsx create mode 100644 plugins/main/public/components/common/doc-viewer/index.ts create mode 100644 plugins/main/public/components/common/doc-viewer/use-doc-viewer.ts create mode 100644 plugins/main/public/components/common/search-bar/index.ts create mode 100644 plugins/main/public/components/common/search-bar/search-bar-service.ts create mode 100644 plugins/main/public/components/common/search-bar/use-search-bar.test.ts create mode 100644 plugins/main/public/components/common/search-bar/use-search-bar.ts create mode 100644 plugins/main/public/components/common/wazuh-discover/config/chart.ts create mode 100644 plugins/main/public/components/common/wazuh-discover/wz-discover.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index b09a7c01f5..02971f0fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 4.9.0 - Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145) +- Remove embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) ## Wazuh v4.8.1 - OpenSearch Dashboards 2.10.0 - Revision 00 diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts new file mode 100644 index 0000000000..4f9315037a --- /dev/null +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -0,0 +1,163 @@ +import { SearchResponse } from "../../../../../../src/core/server"; +import * as FileSaver from '../../../services/file-saver'; +import { beautifyDate } from "../../agents/vuls/inventory/lib"; +import { SearchParams, search } from "../search-bar/search-bar-service"; +import { IFieldType, IndexPattern } from "../../../../../../src/plugins/data/common"; +export const MAX_ENTRIES_PER_QUERY = 10000; +import { EuiDataGridColumn } from '@elastic/eui'; + +export const parseData = (resultsHits: SearchResponse['hits']['hits']): any[] => { + const data = resultsHits.map((hit) => { + if (!hit) { + return {} + } + const source = hit._source as object; + const data = { + ...source, + _id: hit._id, + _index: hit._index, + _type: hit._type, + _score: hit._score, + }; + return data; + }); + return data; +} + + +export const getFieldFormatted = (rowIndex, columnId, indexPattern, rowsParsed) => { + const field = indexPattern.fields.find((field) => field.name === columnId); + let fieldValue = null; + if (columnId.includes('.')) { + // when the column is a nested field. The column could have 2 to n levels + // get dinamically the value of the nested field + const nestedFields = columnId.split('.'); + fieldValue = rowsParsed[rowIndex]; + nestedFields.forEach((field) => { + if (fieldValue) { + fieldValue = fieldValue[field]; + } + }); + } else { + const rowValue = rowsParsed[rowIndex]; + // when not exist the column in the row value then the value is null + if(!rowValue.hasOwnProperty(columnId)){ + fieldValue = null; + }else{ + fieldValue = rowValue[columnId]?.formatted || rowValue[columnId]; + } + } + // when fieldValue is null or undefined then return a empty string + if (fieldValue === null || fieldValue === undefined) { + return ''; + } + // if is date field + if (field?.type === 'date') { + // @ts-ignore + fieldValue = beautifyDate(fieldValue); + } + return fieldValue; +} + +// receive search params +export const exportSearchToCSV = async (params: SearchParams): Promise => { + const DEFAULT_MAX_SIZE_PER_CALL = 1000; + const { indexPattern, filters = [], query, sorting, fields, pagination } = params; + // when the pageSize is greater than the default max size per call (10000) + // then we need to paginate the search + const mustPaginateSearch = pagination?.pageSize && pagination?.pageSize > DEFAULT_MAX_SIZE_PER_CALL; + const pageSize = mustPaginateSearch ? DEFAULT_MAX_SIZE_PER_CALL : pagination?.pageSize; + const totalHits = pagination?.pageSize || DEFAULT_MAX_SIZE_PER_CALL; + let pageIndex = params.pagination?.pageIndex || 0; + let hitsCount = 0; + let allHits = []; + let searchResults; + if (mustPaginateSearch) { + // paginate the search + while (hitsCount < totalHits && Ā hitsCount < MAX_ENTRIES_PER_QUERY) { + const searchParams = { + indexPattern, + filters, + query, + pagination: { + pageIndex, + pageSize, + }, + sorting, + fields, + }; + searchResults = await search(searchParams); + allHits = allHits.concat(searchResults.hits.hits); + hitsCount = allHits.length; + pageIndex++; + } + } else { + searchResults = await search(params); + allHits = searchResults.hits.hits; + } + + const resultsFields = fields; + const data = allHits.map((hit) => { + // check if the field type is a date + const dateFields = indexPattern.fields.getByType('date'); + const dateFieldsNames = dateFields.map((field) => field.name); + const flattenHit = indexPattern.flattenHit(hit); + // replace the date fields with the formatted date + dateFieldsNames.forEach((field) => { + if (flattenHit[field]) { + flattenHit[field] = beautifyDate(flattenHit[field]); + } + }); + return flattenHit; + }); + + if (!resultsFields || resultsFields.length === 0){ + return; + } + + if (!data || data.length === 0) + return; + + const parsedData = data.map((row) => { + const parsedRow = resultsFields?.map((field) => { + const value = row[field]; + if (value === undefined || value === null) { + return ''; + } + if (typeof value === 'object') { + return JSON.stringify(value); + } + return `"${value}"`; + }); + return parsedRow?.join(','); + }).join('\n'); + + // create a csv file using blob + const blobData = new Blob( + [ + `${resultsFields?.join(',')}\n${parsedData}` + ], + { type: 'text/csv' } + ); + + if (blobData) { + // @ts-ignore + FileSaver?.saveAs(blobData, `events-${new Date().toISOString()}.csv`); + } +} + +export const parseColumns = (fields: IFieldType[]): EuiDataGridColumn[] => { + // remove _source field becuase is a object field and is not supported + fields = fields.filter((field) => field.name !== '_source'); + return fields.map((field) => { + return { + ...field, + id: field.name, + display: field.name, + schema: field.type, + actions: { + showHide: true, + }, + }; + }) || []; +} diff --git a/plugins/main/public/components/common/data-grid/index.ts b/plugins/main/public/components/common/data-grid/index.ts new file mode 100644 index 0000000000..172d61d1f0 --- /dev/null +++ b/plugins/main/public/components/common/data-grid/index.ts @@ -0,0 +1,2 @@ +export * from './data-grid-service'; +export * from './use-data-grid'; \ No newline at end of file diff --git a/plugins/main/public/components/common/data-grid/use-data-grid.ts b/plugins/main/public/components/common/data-grid/use-data-grid.ts new file mode 100644 index 0000000000..a27cb3d2d0 --- /dev/null +++ b/plugins/main/public/components/common/data-grid/use-data-grid.ts @@ -0,0 +1,103 @@ +import { EuiDataGridCellValueElementProps, EuiDataGridColumn, EuiDataGridProps, EuiDataGridSorting } from "@elastic/eui" +import React, { useEffect, useMemo, useState, Fragment } from "react"; +import { SearchResponse } from "@opensearch-project/opensearch/api/types"; +// ToDo: check how create this methods +import { parseData, getFieldFormatted, parseColumns } from './data-grid-service'; +import { IndexPattern } from '../../../../../../src/plugins/data/common'; + +const MAX_ENTRIES_PER_QUERY = 10000; + +export type tDataGridColumn = { + render?: (value: any) => string | React.ReactNode; +} & EuiDataGridColumn; + +type tDataGridProps = { + indexPattern: IndexPattern; + results: SearchResponse; + defaultColumns: tDataGridColumn[]; + DocViewInspectButton: ({ rowIndex }: EuiDataGridCellValueElementProps) => React.JSX.Element + ariaLabelledBy: string; +}; + + +export const useDataGrid = (props: tDataGridProps): EuiDataGridProps => { + const { indexPattern, DocViewInspectButton, results, defaultColumns } = props; + /** Columns **/ + const [columns, setColumns] = useState(defaultColumns); + const [columnVisibility, setVisibility] = useState(() => + columns.map(({ id }) => id) + ); + /** Rows */ + const [rows, setRows] = useState([]); + const rowCount = results ? results?.hits?.total as number : 0; + /** Sorting **/ + // get default sorting from default columns + const getDefaultSorting = () => { + const defaultSort = columns.find((column) => column.isSortable || column.defaultSortDirection); + return defaultSort ? [{ id: defaultSort.id, direction: defaultSort.defaultSortDirection || 'desc' }] : []; + } + const defaultSorting: EuiDataGridSorting['columns'] = getDefaultSorting(); + const [sortingColumns, setSortingColumns] = useState(defaultSorting); + const onSort = (sortingColumns) => { setSortingColumns(sortingColumns) }; + /** Pagination **/ + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 20 }); + const onChangeItemsPerPage = useMemo(() => (pageSize) => + setPagination((pagination) => ({ + ...pagination, + pageSize, + pageIndex: 0, + })), [rows, rowCount]); + const onChangePage = (pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })) + + useEffect(() => { + setRows(results?.hits?.hits || []) + }, [results, results?.hits, results?.hits?.total]) + + useEffect(() => { + setPagination((pagination) => ({ ...pagination, pageIndex: 0 })); + }, [rowCount]) + + const renderCellValue = ({ rowIndex, columnId, setCellProps }) => { + const rowsParsed = parseData(rows); + // On the context data always is stored the current page data (pagination) + // then the rowIndex is relative to the current page + const relativeRowIndex = rowIndex % pagination.pageSize; + if(rowsParsed.hasOwnProperty(relativeRowIndex)){ + const fieldFormatted = getFieldFormatted(relativeRowIndex, columnId, indexPattern, rowsParsed); + // check if column have render method initialized + const column = columns.find((column) => column.id === columnId); + if (column && column.render) { + return column.render(fieldFormatted); + } + return fieldFormatted; + } + return null + }; + + const leadingControlColumns = useMemo(() => { + return [ + { + id: 'inspectCollapseColumn', + headerCellRender: () => null, + rowCellRender: (props) => DocViewInspectButton({ ...props, rowIndex: props.rowIndex % pagination.pageSize }), + width: 40, + }, + ]; + }, [results]); + + return { + "aria-labelledby": props.ariaLabelledBy, + columns: parseColumns(indexPattern?.fields || []), + columnVisibility: { visibleColumns: columnVisibility, setVisibleColumns: setVisibility }, + renderCellValue: renderCellValue, + leadingControlColumns: leadingControlColumns, + rowCount: rowCount < MAX_ENTRIES_PER_QUERY ? rowCount : MAX_ENTRIES_PER_QUERY, + sorting: { columns: sortingColumns, onSort }, + pagination: { + ...pagination, + pageSizeOptions: [20, 50, 100], + onChangeItemsPerPage: onChangeItemsPerPage, + onChangePage: onChangePage, + } + } +} \ No newline at end of file diff --git a/plugins/main/public/components/common/doc-viewer/doc-viewer.tsx b/plugins/main/public/components/common/doc-viewer/doc-viewer.tsx new file mode 100644 index 0000000000..79ef91eda1 --- /dev/null +++ b/plugins/main/public/components/common/doc-viewer/doc-viewer.tsx @@ -0,0 +1,150 @@ +import React, { useState } from 'react'; +import classNames from 'classnames'; +import { escapeRegExp } from 'lodash'; +import { i18n } from '@osd/i18n'; +import { FieldIcon } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; +import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; + +const COLLAPSE_LINE_LENGTH = 350; +const DOT_PREFIX_RE = /(.).+?\./g; + +export type tDocViewerProps = { + flattened: any; + formatted: any; + mapping: any; + indexPattern: any; +} + +/** + * Convert a dot.notated.string into a short + * version (d.n.string) + */ +export const shortenDottedString = (input: string) => input.replace(DOT_PREFIX_RE, '$1.'); + +export const getFieldTypeName = (type: string) => { + switch (type) { + case 'boolean': + return i18n.translate('discover.fieldNameIcons.booleanAriaLabel', { + defaultMessage: 'Boolean field', + }); + case 'conflict': + return i18n.translate('discover.fieldNameIcons.conflictFieldAriaLabel', { + defaultMessage: 'Conflicting field', + }); + case 'date': + return i18n.translate('discover.fieldNameIcons.dateFieldAriaLabel', { + defaultMessage: 'Date field', + }); + case 'geo_point': + return i18n.translate('discover.fieldNameIcons.geoPointFieldAriaLabel', { + defaultMessage: 'Geo point field', + }); + case 'geo_shape': + return i18n.translate('discover.fieldNameIcons.geoShapeFieldAriaLabel', { + defaultMessage: 'Geo shape field', + }); + case 'ip': + return i18n.translate('discover.fieldNameIcons.ipAddressFieldAriaLabel', { + defaultMessage: 'IP address field', + }); + case 'murmur3': + return i18n.translate('discover.fieldNameIcons.murmur3FieldAriaLabel', { + defaultMessage: 'Murmur3 field', + }); + case 'number': + return i18n.translate('discover.fieldNameIcons.numberFieldAriaLabel', { + defaultMessage: 'Number field', + }); + case 'source': + // Note that this type is currently not provided, type for _source is undefined + return i18n.translate('discover.fieldNameIcons.sourceFieldAriaLabel', { + defaultMessage: 'Source field', + }); + case 'string': + return i18n.translate('discover.fieldNameIcons.stringFieldAriaLabel', { + defaultMessage: 'String field', + }); + case 'nested': + return i18n.translate('discover.fieldNameIcons.nestedFieldAriaLabel', { + defaultMessage: 'Nested field', + }); + default: + return i18n.translate('discover.fieldNameIcons.unknownFieldAriaLabel', { + defaultMessage: 'Unknown field', + }); + } +} + +const DocViewer = (props: tDocViewerProps) => { + const [fieldRowOpen, setFieldRowOpen] = useState({} as Record); + const { flattened, formatted, mapping, indexPattern } = props; + + return (<> + {flattened && ( + + + {Object.keys(flattened) + .sort() + .map((field, index) => { + const value = String(formatted[field]); + const fieldMapping = mapping(field); + const isCollapsible = value.length > COLLAPSE_LINE_LENGTH; + const isCollapsed = isCollapsible && !fieldRowOpen[field]; + const valueClassName = classNames({ + // eslint-disable-next-line @typescript-eslint/naming-convention + osdDocViewer__value: true, + 'truncate-by-height': isCollapsible && isCollapsed, + }); + const isNestedField = + !indexPattern.fields.getByName(field) && + !!indexPattern.fields.getAll().find((patternField) => { + // We only want to match a full path segment + const nestedRootRegex = new RegExp(escapeRegExp(field) + '(\\.|$)'); + return nestedRootRegex.test(patternField.subType?.nested?.path ?? ''); + }); + const fieldType = isNestedField ? 'nested' : indexPattern.fields.getByName(field)?.type; + const typeName = getFieldTypeName(String(fieldType)); + const displayName = field; + const fieldIconProps = { fill: 'none', color: 'gray' } + const scripted = Boolean(fieldMapping?.scripted) + + return ( + + + + + ); + })} + +
+ + + + + + + {displayName} + + + + +
+
+ )}) +}; + +export default DocViewer; \ No newline at end of file diff --git a/plugins/main/public/components/common/doc-viewer/index.ts b/plugins/main/public/components/common/doc-viewer/index.ts new file mode 100644 index 0000000000..a8231e35e6 --- /dev/null +++ b/plugins/main/public/components/common/doc-viewer/index.ts @@ -0,0 +1,2 @@ +export * from './use-doc-viewer'; +export * from './doc-viewer'; \ No newline at end of file diff --git a/plugins/main/public/components/common/doc-viewer/use-doc-viewer.ts b/plugins/main/public/components/common/doc-viewer/use-doc-viewer.ts new file mode 100644 index 0000000000..61a0b3af2d --- /dev/null +++ b/plugins/main/public/components/common/doc-viewer/use-doc-viewer.ts @@ -0,0 +1,28 @@ +import { tDocViewerProps } from "./doc-viewer" +import { IndexPattern } from "../../../../../../src/plugins/data/common"; + +type tUseDocViewerInputs = { + indexPattern: IndexPattern; + doc: any; +} + +export const useDocViewer = (props: tUseDocViewerInputs): tDocViewerProps => { + const { indexPattern, doc } = props; + + if (!indexPattern || !doc) { + return { + flattened: {}, + formatted: {}, + indexPattern: undefined, + mapping: undefined + } + } + + const mapping = indexPattern?.fields.getByName; + return { + flattened: indexPattern?.flattenHit(doc), + formatted: indexPattern?.formatHit(doc, 'html'), + indexPattern, + mapping + } +} \ No newline at end of file diff --git a/plugins/main/public/components/common/search-bar/index.ts b/plugins/main/public/components/common/search-bar/index.ts new file mode 100644 index 0000000000..104abb8280 --- /dev/null +++ b/plugins/main/public/components/common/search-bar/index.ts @@ -0,0 +1,2 @@ +export * from './search-bar-service'; +export * from './use-search-bar'; \ No newline at end of file diff --git a/plugins/main/public/components/common/search-bar/search-bar-service.ts b/plugins/main/public/components/common/search-bar/search-bar-service.ts new file mode 100644 index 0000000000..d7c546ccff --- /dev/null +++ b/plugins/main/public/components/common/search-bar/search-bar-service.ts @@ -0,0 +1,56 @@ +import { getPlugins } from '../../../kibana-services'; +import { IndexPattern, Filter, OpenSearchQuerySortValue } from "../../../../../../src/plugins/data/public"; +import { SearchResponse } from "../../../../../../src/core/server"; + +export interface SearchParams { + indexPattern: IndexPattern; + filters?: Filter[]; + query?: any; + pagination?: { + pageIndex?: number; + pageSize?: number; + }; + fields?: string[], + sorting?: { + columns: { + id: string; + direction: 'asc' | 'desc'; + }[]; + }; +} + +export const search = async (params: SearchParams): Promise => { + const { indexPattern, filters = [], query, pagination, sorting, fields } = params; + if(!indexPattern){ + return; + } + const data = getPlugins().data; + const searchSource = await data.search.searchSource.create(); + const fromField = (pagination?.pageIndex || 0) * (pagination?.pageSize || 100); + const sortOrder: OpenSearchQuerySortValue[] = sorting?.columns.map((column) => { + const sortDirection = column.direction === 'asc' ? 'asc' : 'desc'; + return { [column?.id || '']: sortDirection } as OpenSearchQuerySortValue; + }) || []; + + const searchParams = searchSource + .setParent(undefined) + .setField('filter', filters) + .setField('query', query) + .setField('sort', sortOrder) + .setField('size', pagination?.pageSize) + .setField('from', fromField) + .setField('index', indexPattern) + + // add fields + if (fields && Array.isArray(fields) && fields.length > 0){ + searchParams.setField('fields', fields); + } + try{ + return await searchParams.fetch(); + }catch(error){ + if(error.body){ + throw error.body; + } + throw error; + } +}; \ No newline at end of file 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 new file mode 100644 index 0000000000..54b740dcd7 --- /dev/null +++ b/plugins/main/public/components/common/search-bar/use-search-bar.test.ts @@ -0,0 +1,219 @@ +import { renderHook } from '@testing-library/react-hooks'; +import '@testing-library/jest-dom/extend-expect'; +// osd dependencies +import { Start, dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { + Filter, + IndexPattern, + Query, + TimeRange, +} from '../../../../../../src/plugins/data/public'; +// wazuh plugin dependencies +import useSearchBar from './use-search-bar'; +import { getDataPlugin } from '../../../kibana-services'; +import * as timeFilterHook from '../hooks/use-time-filter'; +import * as queryManagerHook from '../hooks/use-query'; + +/** + * Mocking Data Plugin + **/ +jest.mock('../../../kibana-services', () => { + return { + getDataPlugin: jest.fn(), + }; +}); +/* using osd mock utils */ +const mockDataPlugin = dataPluginMock.createStartContract(); +const mockedGetDataPlugin = getDataPlugin as jest.Mock; +mockedGetDataPlugin.mockImplementation( + () => + ({ + ...mockDataPlugin, + ...{ + query: { + ...mockDataPlugin.query, + queryString: { + ...mockDataPlugin.query.queryString, + getUpdates$: jest.fn(() => ({ + subscribe: jest.fn(), + unsubscribe: jest.fn(), + })), + }, + }, + }, + } as Start) +); +/////////////////////////////////////////////////////////// + +const mockedDefaultIndexPatternData: Partial = { + // used partial not avoid fill all the interface, it's only for testing purpose + id: 'default-index-pattern', + title: '', +}; + +describe('[hook] useSearchBarConfiguration', () => { + beforeAll(() => { + /***** mock use-time-filter hook *****/ + const spyUseTimeFilter = jest.spyOn(timeFilterHook, 'useTimeFilter'); + const mockTimeFilterResult: TimeRange = { + from: 'now/d', + to: 'now/d', + }; + spyUseTimeFilter.mockImplementation(() => ({ + timeFilter: mockTimeFilterResult, + setTimeFilter: jest.fn(), + timeHistory: [], + })); + /***** mock use-time-filter hook *****/ + const spyUseQueryManager = jest.spyOn(queryManagerHook, 'useQueryManager'); + const mockQueryResult: Query = { + language: 'kuery', + query: '', + }; + spyUseQueryManager.mockImplementation(() => [mockQueryResult, jest.fn()]); + }); + + it.only('should return default app index pattern when not receiving a default index pattern', async () => { + jest + .spyOn(mockDataPlugin.indexPatterns, 'getDefault') + .mockResolvedValue(mockedDefaultIndexPatternData); + jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]); + const { result, waitForNextUpdate } = renderHook(() => useSearchBar({})); + await waitForNextUpdate(); + expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled(); + expect(result.current.searchBarProps.indexPatterns).toMatchObject([ + mockedDefaultIndexPatternData, + ]); + }); + + it('should return the same index pattern when receiving a default index pattern', async () => { + const exampleIndexPatternId = 'wazuh-index-pattern'; + const mockedIndexPatternData: Partial = { + // used partial not avoid fill all the interface, it's only for testing purpose + id: exampleIndexPatternId, + title: '', + }; + jest.spyOn(mockDataPlugin.indexPatterns, 'get').mockResolvedValue(mockedIndexPatternData); + const { result, waitForNextUpdate } = renderHook(() => + useSearchBar({ + defaultIndexPatternID: 'wazuh-index-pattern', + }) + ); + await waitForNextUpdate(); + expect(mockDataPlugin.indexPatterns.get).toBeCalledWith(exampleIndexPatternId); + expect(result.current.searchBarProps.indexPatterns).toMatchObject([mockedIndexPatternData]); + }); + + it('should show an ERROR message and get the default app index pattern when not found the index pattern data by the ID received', async () => { + const INDEX_NOT_FOUND_ERROR = new Error('Index Pattern not found'); + jest.spyOn(mockDataPlugin.indexPatterns, 'get').mockImplementation(() => { + throw INDEX_NOT_FOUND_ERROR; + }); + jest + .spyOn(mockDataPlugin.indexPatterns, 'getDefault') + .mockResolvedValue(mockedDefaultIndexPatternData); + jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]); + + // mocking console error to avoid logs in test and check if is called + const mockedConsoleError = jest.spyOn(console, 'error').mockImplementationOnce(() => {}); + const { result, waitForNextUpdate } = renderHook(() => + useSearchBar({ + defaultIndexPatternID: 'invalid-index-pattern-id', + }) + ); + + await waitForNextUpdate(); + expect(mockDataPlugin.indexPatterns.getDefault).toBeCalled(); + expect(mockDataPlugin.indexPatterns.get).toBeCalledWith('invalid-index-pattern-id'); + expect(result.current.searchBarProps.indexPatterns).toMatchObject([ + mockedDefaultIndexPatternData, + ]); + expect(mockedConsoleError).toBeCalledWith(INDEX_NOT_FOUND_ERROR); + }); + + it('should return the same filters and apply them to the filter manager when are received by props', async () => { + const defaultFilters: Filter[] = [ + { + query: 'something to filter', + meta: { + alias: 'filter-mocked', + disabled: false, + negate: true, + }, + }, + ]; + jest + .spyOn(mockDataPlugin.indexPatterns, 'getDefault') + .mockResolvedValue(mockedDefaultIndexPatternData); + jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue(defaultFilters); + const { result, waitForNextUpdate } = renderHook(() => + useSearchBar({ + filters: defaultFilters, + }) + ); + + await waitForNextUpdate(); + + expect(result.current.searchBarProps.filters).toMatchObject(defaultFilters); + expect(mockDataPlugin.query.filterManager.setFilters).toBeCalledWith(defaultFilters); + expect(mockDataPlugin.query.filterManager.getFilters).toBeCalled(); + }); + + it('should return and preserve filters when the index pattern received is equal to the index pattern already selected in the app', async () => { + const defaultIndexFilters: Filter[] = [ + { + query: 'something to filter', + meta: { + alias: 'filter-mocked', + disabled: false, + negate: true, + }, + }, + ]; + jest + .spyOn(mockDataPlugin.indexPatterns, 'getDefault') + .mockResolvedValue(mockedDefaultIndexPatternData); + jest + .spyOn(mockDataPlugin.indexPatterns, 'get') + .mockResolvedValue(mockedDefaultIndexPatternData); + jest + .spyOn(mockDataPlugin.query.filterManager, 'getFilters') + .mockReturnValue(defaultIndexFilters); + const { result, waitForNextUpdate } = renderHook(() => + useSearchBar({ + defaultIndexPatternID: mockedDefaultIndexPatternData.id, + }) + ); + await waitForNextUpdate(); + expect(result.current.searchBarProps.indexPatterns).toMatchObject([ + mockedDefaultIndexPatternData, + ]); + expect(result.current.searchBarProps.filters).toMatchObject(defaultIndexFilters); + }); + + it('should return empty filters when the index pattern is NOT equal to the default app index pattern', async () => { + const exampleIndexPatternId = 'wazuh-index-pattern'; + const mockedExampleIndexPatternData: Partial = { + // used partial not avoid fill all the interface, it's only for testing purpose + id: exampleIndexPatternId, + title: '', + }; + jest + .spyOn(mockDataPlugin.indexPatterns, 'get') + .mockResolvedValue(mockedExampleIndexPatternData); + jest + .spyOn(mockDataPlugin.indexPatterns, 'getDefault') + .mockResolvedValue(mockedDefaultIndexPatternData); + jest.spyOn(mockDataPlugin.query.filterManager, 'getFilters').mockReturnValue([]); + const { result, waitForNextUpdate } = renderHook(() => + useSearchBar({ + defaultIndexPatternID: exampleIndexPatternId, + }) + ); + await waitForNextUpdate(); + expect(result.current.searchBarProps.indexPatterns).toMatchObject([ + mockedExampleIndexPatternData, + ]); + expect(result.current.searchBarProps.filters).toStrictEqual([]); + }); +}); 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 new file mode 100644 index 0000000000..30025f9676 --- /dev/null +++ b/plugins/main/public/components/common/search-bar/use-search-bar.ts @@ -0,0 +1,173 @@ +import { useEffect, useState } from 'react'; +import { + SearchBarProps, + FilterManager, + TimeRange, + Query, + Filter, + IIndexPattern, + IndexPatternsContract, +} from '../../../../../../src/plugins/data/public'; +import { getDataPlugin } from '../../../kibana-services'; + +import { useFilterManager, useQueryManager, useTimeFilter } from '../hooks'; +import { AUTHORIZED_AGENTS } from '../../../../common/constants'; + +type tUseSearchBarCustomInputs = { + defaultIndexPatternID: IIndexPattern['id']; + onFiltersUpdated?: (filters: Filter[]) => void; + onQuerySubmitted?: ( + payload: { dateRange: TimeRange; query?: Query }, + isUpdate?: boolean, + ) => void; +}; +type tUseSearchBarProps = Partial & tUseSearchBarCustomInputs; + +// Output types +type tUserSearchBarResponse = { + searchBarProps: Partial; +}; + +/** + * Hook used to compose the searchbar configuration props + * @param props + * @returns + */ +const useSearchBar = ( + props?: tUseSearchBarProps, +): tUserSearchBarResponse => { + // dependencies + const filterManager = useFilterManager().filterManager as FilterManager; + const { filters } = useFilterManager(); + const [query, setQuery] = props?.query + ? useState(props?.query) + : useQueryManager(); + const { timeFilter, timeHistory, setTimeFilter } = useTimeFilter(); + // states + const [isLoading, setIsLoading] = useState(false); + const [indexPatternSelected, setIndexPatternSelected] = + useState(); + + useEffect(() => { + initSearchBar(); + }, []); + + useEffect(() => { + const defaultIndex = props?.defaultIndexPatternID; + /* Filters that do not belong to the default index are filtered */ + const cleanedFilters = filters.filter( + filter => filter.meta.index === defaultIndex, + ); + if (cleanedFilters.length !== filters.length) { + filterManager.setFilters(cleanedFilters); + } + }, [filters]); + + /** + * Initialize the searchbar props with the corresponding index pattern and filters + */ + const initSearchBar = async () => { + setIsLoading(true); + const indexPattern = await getIndexPattern(props?.defaultIndexPatternID); + setIndexPatternSelected(indexPattern); + const filters = await getInitialFilters(indexPattern); + filterManager.setFilters(filters); + setIsLoading(false); + }; + + /** + * Return the index pattern data by ID. + * If not receive a ID return the default index from the index pattern service + * @returns + */ + const getIndexPattern = async (indexPatternID?: string) => { + const indexPatternService = getDataPlugin() + .indexPatterns as IndexPatternsContract; + if (indexPatternID) { + try { + return await indexPatternService.get(indexPatternID); + } catch (error) { + // when the index pattern id not exists will get the default defined in the index pattern service + console.error(error); + return await indexPatternService.getDefault(); + } + } else { + return await indexPatternService.getDefault(); + } + }; + + /** + * Return the initial filters considering if hook receives initial filters + * When the default index pattern is the same like the received preserve the filters + * @param indexPattern + * @returns + */ + const getInitialFilters = async (indexPattern: IIndexPattern) => { + const indexPatternService = getDataPlugin() + .indexPatterns as IndexPatternsContract; + let initialFilters: Filter[] = []; + if (props?.filters) { + return props?.filters; + } + if (indexPattern) { + // get filtermanager and filters + // if the index is the same, get filters stored + // else clear filters + const defaultIndexPattern = + (await indexPatternService.getDefault()) as IIndexPattern; + initialFilters = + defaultIndexPattern.id === indexPattern.id + ? filterManager.getFilters() + : []; + } else { + initialFilters = []; + } + return initialFilters; + }; + + /** + * Return filters from filters manager. + * Additionally solve the known issue with the auto loaded agent.id filters from the searchbar + * @returns + */ + const getFilters = () => { + const filters = filterManager ? filterManager.getFilters() : []; + return filters.filter( + filter => filter.meta.controlledBy !== AUTHORIZED_AGENTS, + ); // remove auto loaded agent.id filters + }; + + /** + * 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(), + query, + timeHistory, + dateRangeFrom: timeFilter.from, + dateRangeTo: timeFilter.to, + onFiltersUpdated: (filters: Filter[]) => { + // its necessary execute setter to apply filters + filterManager.setFilters(filters); + props?.onFiltersUpdated && props?.onFiltersUpdated(filters); + }, + onQuerySubmit: ( + payload: { dateRange: TimeRange; query?: Query }, + _isUpdate?: boolean, + ): void => { + const { dateRange, query } = payload; + // its necessary execute setter to apply query filters + setTimeFilter(dateRange); + setQuery(query); + props?.onQuerySubmitted && props?.onQuerySubmitted(payload); + }, + }; + + return { + searchBarProps, + }; +}; + +export default useSearchBar; diff --git a/plugins/main/public/components/common/wazuh-discover/config/chart.ts b/plugins/main/public/components/common/wazuh-discover/config/chart.ts new file mode 100644 index 0000000000..8e667141b0 --- /dev/null +++ b/plugins/main/public/components/common/wazuh-discover/config/chart.ts @@ -0,0 +1,130 @@ +import { DashboardPanelState } from "../../../../../../../src/plugins/dashboard/public/application"; +import { EmbeddableInput } from "../../../../../../../src/plugins/embeddable/public"; + +const hitsHistogram = (indexPatternId: string) => { + return { + id: 'events_histogram', + title: 'Events histogram', + type: 'horizontal_bar', + params: { + type: 'histogram', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'bottom', + show: true, + style: {}, + scale: { type: 'linear' }, + labels: { show: true, filter: true, truncate: 100 }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'left', + show: true, + style: {}, + scale: { type: 'linear', mode: 'normal' }, + labels: { show: true, rotate: 0, filter: false, truncate: 100 }, + title: { text: 'Count' }, + }, + ], + seriesParams: [ + { + show: true, + type: 'histogram', + mode: 'stacked', + data: { + label: 'Count', + id: '1', + }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true, + }, + ], + addTooltip: true, + addLegend: false, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { id: '1', enabled: true, type: 'count', schema: 'metric', params: {} }, + { + id: '2', + enabled: true, + type: 'date_histogram', + schema: 'segment', + params: { + field: 'timestamp', + useNormalizedEsInterval: true, + scaleMetricValues: false, + interval: 'auto', + drop_partials: false, + min_doc_count: 1, + extended_bounds: {}, + }, + }, + ], + }, + }; +}; + + +export const getDiscoverPanels = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + '1': { + gridData: { + w: 100, + h: 10, + x: 0, + y: 0, + i: '1', + }, + type: 'visualization', + explicitInput: { + id: '1', + savedVis: hitsHistogram(indexPatternId), + }, + } + }; +} diff --git a/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx new file mode 100644 index 0000000000..b33ac2ed87 --- /dev/null +++ b/plugins/main/public/components/common/wazuh-discover/wz-discover.tsx @@ -0,0 +1,245 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import { + EuiDataGrid, + EuiPageTemplate, + EuiToolTip, + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiButtonEmpty, + EuiSpacer, + EuiPanel, +} from '@elastic/eui'; +import { IntlProvider } from 'react-intl'; +import { IndexPattern } from '../../../../../../src/plugins/data/common'; +import { SearchResponse } from '../../../../../../src/core/server'; +import { useDocViewer } from '../doc-viewer'; +import DocViewer from '../doc-viewer/doc-viewer'; +import { DiscoverNoResults } from '../../overview/vulnerabilities/common/components/no_results'; +import { LoadingSpinner } from '../../overview/vulnerabilities/common/components/loading_spinner'; +import { useDataGrid, tDataGridColumn, exportSearchToCSV } from '../data-grid'; +import { ErrorHandler, ErrorFactory, HttpError } from '../../../react-services/error-management'; +import { withErrorBoundary } from '../hocs'; +import { HitsCounter } from '../../../kibana-integrations/discover/application/components/hits_counter'; +import { formatNumWithCommas } from '../../../kibana-integrations/discover/application/helpers'; +import useSearchBar from '../search-bar/use-search-bar'; +import { search } from '../search-bar'; +import { getPlugins } from '../../../kibana-services'; +import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; +import { getDiscoverPanels } from './config/chart'; +const DashboardByRenderer = getPlugins().dashboard.DashboardContainerByValueRenderer; + +/** + * ToDo: + * - add possibility to customize column render + * - add save query feature + */ + +export const MAX_ENTRIES_PER_QUERY = 10000; + +type WazuhDiscoverProps = { + indexPatternName: string; + tableColumns: tDataGridColumn[]; +} + +const WazuhDiscover = (props: WazuhDiscoverProps) => { + const { indexPatternName, tableColumns: defaultTableColumns } = props + const { searchBarProps } = useSearchBar({ + defaultIndexPatternID: indexPatternName, + }) + const { isLoading, filters, query, indexPatterns } = searchBarProps; + const SearchBar = getPlugins().data.ui.SearchBar; + const [results, setResults] = useState({} as SearchResponse); + const [inspectedHit, setInspectedHit] = useState(undefined); + const [indexPattern, setIndexPattern] = useState(undefined); + const [isSearching, setIsSearching] = useState(false); + const [isExporting, setIsExporting] = useState(false); + + const onClickInspectDoc = useMemo(() => (index: number) => { + const rowClicked = results.hits.hits[index]; + setInspectedHit(rowClicked); + }, [results]); + + const DocViewInspectButton = ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const inspectHintMsg = 'Inspect document details'; + return ( + + onClickInspectDoc(rowIndex)} + iconType='inspect' + aria-label={inspectHintMsg} + /> + + ); + }; + + const dataGridProps = useDataGrid({ + ariaLabelledBy: 'Discover events table', + defaultColumns: defaultTableColumns, + results, + indexPattern: indexPattern as IndexPattern, + DocViewInspectButton + }) + + const { pagination, sorting, columnVisibility } = dataGridProps; + + const docViewerProps = useDocViewer({ + doc: inspectedHit, + indexPattern: indexPattern as IndexPattern, + }) + + useEffect(() => { + if (!isLoading) { + setIndexPattern(indexPatterns?.[0] as IndexPattern); + search({ + indexPattern: indexPatterns?.[0] as IndexPattern, + filters, + query, + pagination, + sorting + }).then((results) => { + setResults(results); + setIsSearching(false); + }).catch((error) => { + const searchError = ErrorFactory.create(HttpError, { error, message: 'Error fetching vulnerabilities' }) + ErrorHandler.handleError(searchError); + setIsSearching(false); + }) + } + }, [JSON.stringify(searchBarProps), JSON.stringify(pagination), JSON.stringify(sorting)]); + + const timeField = indexPattern?.timeFieldName ? indexPattern.timeFieldName : undefined; + + const onClickExportResults = async () => { + const params = { + indexPattern: indexPatterns?.[0] as IndexPattern, + filters, + query, + fields: columnVisibility.visibleColumns, + pagination: { + pageIndex: 0, + pageSize: results.hits.total + }, + sorting + } + try { + setIsExporting(true); + await exportSearchToCSV(params); + } catch (error) { + const searchError = ErrorFactory.create(HttpError, { error, message: 'Error downloading csv report' }) + ErrorHandler.handleError(searchError); + } finally { + setIsExporting(false); + } + } + + return ( + + + <> + {isLoading ? + : + } + {isSearching ? + : null} + {!isLoading && !isSearching && results?.hits?.total === 0 ? + : null} + {!isLoading && !isSearching && results?.hits?.total > 0 ? ( + <> + + + + + + + + + + { }} + tooltip={results?.hits?.total && results?.hits?.total > MAX_ENTRIES_PER_QUERY ? { + ariaLabel: 'Warning', + content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas(MAX_ENTRIES_PER_QUERY)} hits.`, + iconType: 'alert', + position: 'top' + } : undefined} + /> + + Export Formated + + + ) + }} + /> + ) : null} + {inspectedHit && ( + setInspectedHit(undefined)} size="m"> + + +

Document Details

+
+
+ + + + + + + +
+ )} + +
+
+ ); +} + +export default WazuhDiscover; \ No newline at end of file From 01cbd74de4d299e030d71e677228a98df6494028 Mon Sep 17 00:00:00 2001 From: Luciano Gorza <103193307+lucianogorza@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:59:49 -0300 Subject: [PATCH 011/136] Create endpoints plugin and unique Endpoints Summary React component (#6230) --- docker/osd-dev/dev.yml | 1 + plugins/main/opensearch_dashboards.json | 9 +- .../common/welcome/overview-welcome.js | 17 +- .../endpoints-summary/endpoints-summary.scss} | 0 .../endpoints-summary/endpoints-summary.tsx | 332 +++ .../endpoints-summary/hooks/endpoints.ts | 37 + .../endpoints-summary/hooks/index.ts | 1 + .../components/endpoints-summary/index.tsx | 86 + .../command-output/command-output.tsx | 0 .../components/group-input/group-input.scss | 0 .../components/group-input/group-input.tsx | 4 +- .../optionals-inputs/optionals-inputs.tsx | 25 +- .../checkbox-group/checkbox-group.scss | 0 .../checkbox-group/checkbox-group.test.tsx | 0 .../checkbox-group/checkbox-group.tsx | 0 .../os-selector/os-card/os-card.scss | 0 .../os-selector/os-card/os-card.test.tsx | 10 +- .../os-selector/os-card/os-card.tsx | 2 +- .../server-address/server-address.tsx | 64 +- .../register-agent/register-agent.scss | 0 .../register-agent/register-agent.tsx | 254 ++ .../containers/steps/steps.scss | 0 .../register-agent/containers/steps/steps.tsx | 6 +- .../core/config/os-commands-definitions.ts | 0 .../core/register-commands/README.md | 0 .../command-generator.test.ts | 0 .../command-generator/command-generator.ts | 2 +- .../register-commands/exceptions/index.ts | 0 .../optional-parameters-manager.test.ts | 0 .../optional-parameters-manager.ts | 0 .../get-install-command.service.test.ts | 0 .../services/get-install-command.service.ts | 0 .../search-os-definitions.service.test.ts | 0 .../services/search-os-definitions.service.ts | 0 .../core/register-commands/types.ts | 0 .../register-agent/hooks/README.md | 0 .../hooks/use-register-agent-commands.test.ts | 0 .../hooks/use-register-agent-commands.ts | 2 +- .../register-agent/index.tsx | 0 .../register-agent/interfaces/types.ts | 0 .../services/form-status-manager.test.tsx | 0 .../services/form-status-manager.tsx | 0 .../register-agent-os-commands-services.tsx | 0 .../services/register-agent-services.test.ts} | 60 +- .../services/register-agent-services.tsx | 90 +- .../register-agent-steps-status-services.tsx | 0 .../services/wazuh-password-service.test.ts | 0 .../services/wazuh-password-service.ts | 0 .../utils/register-agent-data.tsx | 14 +- .../register-agent/utils/validations.test.tsx | 0 .../register-agent/utils/validations.tsx | 0 .../__snapshots__/agents-table.test.tsx.snap} | 341 +-- .../endpoints-summary/table}/agents-table.js | 43 +- .../table/agents-table.test.tsx} | 9 + .../controllers/agent/agents-preview.js | 333 --- .../agent/components/agents-preview.js | 413 --- .../agent/components/checkUpgrade.tsx | 98 - .../components/register-agent-service.ts | 218 -- .../agent/components/register-agent.js | 2291 ----------------- .../agent/components/register-agent.scss | 14 - .../agent/components/register-agent.test.js | 33 - .../agent/components/wz-accordion.tsx | 57 - .../main/public/controllers/agent/index.js | 12 +- .../register-agent-button-group.test.tsx.snap | 138 - .../register-agent-button-group.test.tsx | 111 - .../register-agent-button-group.tsx | 54 - .../server-address.test.tsx.snap | 84 - .../agent/register-agent/steps/index.ts | 1 - .../steps/server-address.test.tsx | 221 -- .../register-agent/steps/server-address.tsx | 170 -- .../steps/wz-manager-address.tsx | 46 - .../controllers/agent/wazuh-config/index.ts | 420 --- .../register-agent/register-agent.tsx | 242 -- plugins/main/public/services/routes.js | 7 + .../templates/agents-prev/agents-prev.html | 77 +- .../templates/agents/deploy/agent-deploy.html | 1 + .../templates/visualize/dashboards.html | 219 +- plugins/main/public/types.ts | 2 + plugins/wazuh-endpoints/.i18nrc.json | 7 + plugins/wazuh-endpoints/common/constants.ts | 4 + plugins/wazuh-endpoints/common/types.ts | 0 .../opensearch_dashboards.json | 9 + plugins/wazuh-endpoints/package.json | 20 + plugins/wazuh-endpoints/public/index.ts | 8 + plugins/wazuh-endpoints/public/plugin.ts | 23 + plugins/wazuh-endpoints/public/types.ts | 5 + plugins/wazuh-endpoints/scripts/jest.js | 19 + plugins/wazuh-endpoints/scripts/manifest.js | 17 + plugins/wazuh-endpoints/scripts/runner.js | 148 ++ plugins/wazuh-endpoints/server/index.ts | 11 + plugins/wazuh-endpoints/server/plugin.ts | 37 + plugins/wazuh-endpoints/server/types.ts | 10 + plugins/wazuh-endpoints/test/jest/config.js | 41 + .../wazuh-endpoints/translations/en-US.json | 79 + plugins/wazuh-endpoints/tsconfig.json | 17 + 95 files changed, 1573 insertions(+), 5553 deletions(-) rename plugins/main/public/{controllers/agent/components/agents-preview.scss => components/endpoints-summary/endpoints-summary.scss} (100%) create mode 100644 plugins/main/public/components/endpoints-summary/endpoints-summary.tsx create mode 100644 plugins/main/public/components/endpoints-summary/hooks/endpoints.ts create mode 100644 plugins/main/public/components/endpoints-summary/hooks/index.ts create mode 100644 plugins/main/public/components/endpoints-summary/index.tsx rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/command-output/command-output.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/group-input/group-input.scss (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/group-input/group-input.tsx (94%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/optionals-inputs/optionals-inputs.tsx (85%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/os-selector/checkbox-group/checkbox-group.scss (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/os-selector/checkbox-group/checkbox-group.test.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/os-selector/checkbox-group/checkbox-group.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/os-selector/os-card/os-card.scss (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/os-selector/os-card/os-card.test.tsx (82%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/os-selector/os-card/os-card.tsx (95%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/components/server-address/server-address.tsx (76%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/containers/register-agent/register-agent.scss (100%) create mode 100644 plugins/main/public/components/endpoints-summary/register-agent/containers/register-agent/register-agent.tsx rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/containers/steps/steps.scss (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/containers/steps/steps.tsx (97%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/config/os-commands-definitions.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/README.md (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/command-generator/command-generator.test.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/command-generator/command-generator.ts (98%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/exceptions/index.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.test.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/services/get-install-command.service.test.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/services/get-install-command.service.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/services/search-os-definitions.service.test.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/services/search-os-definitions.service.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/core/register-commands/types.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/hooks/README.md (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/hooks/use-register-agent-commands.test.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/hooks/use-register-agent-commands.ts (98%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/index.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/interfaces/types.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/services/form-status-manager.test.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/services/form-status-manager.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/services/register-agent-os-commands-services.tsx (100%) rename plugins/main/public/{controllers/agent/components/register-agent-service.test.ts => components/endpoints-summary/register-agent/services/register-agent-services.test.ts} (84%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/services/register-agent-services.tsx (77%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/services/register-agent-steps-status-services.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/services/wazuh-password-service.test.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/services/wazuh-password-service.ts (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/utils/register-agent-data.tsx (64%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/utils/validations.test.tsx (100%) rename plugins/main/public/{controllers => components/endpoints-summary}/register-agent/utils/validations.tsx (100%) rename plugins/main/public/{controllers/agent/components/__snapshots__/agent-table.test.tsx.snap => components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap} (85%) rename plugins/main/public/{controllers/agent/components => components/endpoints-summary/table}/agents-table.js (93%) rename plugins/main/public/{controllers/agent/components/agent-table.test.tsx => components/endpoints-summary/table/agents-table.test.tsx} (98%) delete mode 100644 plugins/main/public/controllers/agent/agents-preview.js delete mode 100644 plugins/main/public/controllers/agent/components/agents-preview.js delete mode 100644 plugins/main/public/controllers/agent/components/checkUpgrade.tsx delete mode 100644 plugins/main/public/controllers/agent/components/register-agent-service.ts delete mode 100644 plugins/main/public/controllers/agent/components/register-agent.js delete mode 100644 plugins/main/public/controllers/agent/components/register-agent.scss delete mode 100644 plugins/main/public/controllers/agent/components/register-agent.test.js delete mode 100644 plugins/main/public/controllers/agent/components/wz-accordion.tsx delete mode 100644 plugins/main/public/controllers/agent/register-agent/__snapshots__/register-agent-button-group.test.tsx.snap delete mode 100644 plugins/main/public/controllers/agent/register-agent/register-agent-button-group.test.tsx delete mode 100644 plugins/main/public/controllers/agent/register-agent/register-agent-button-group.tsx delete mode 100644 plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap delete mode 100644 plugins/main/public/controllers/agent/register-agent/steps/index.ts delete mode 100644 plugins/main/public/controllers/agent/register-agent/steps/server-address.test.tsx delete mode 100644 plugins/main/public/controllers/agent/register-agent/steps/server-address.tsx delete mode 100644 plugins/main/public/controllers/agent/register-agent/steps/wz-manager-address.tsx delete mode 100644 plugins/main/public/controllers/agent/wazuh-config/index.ts delete mode 100644 plugins/main/public/controllers/register-agent/containers/register-agent/register-agent.tsx create mode 100644 plugins/main/public/templates/agents/deploy/agent-deploy.html create mode 100644 plugins/wazuh-endpoints/.i18nrc.json create mode 100644 plugins/wazuh-endpoints/common/constants.ts create mode 100644 plugins/wazuh-endpoints/common/types.ts create mode 100644 plugins/wazuh-endpoints/opensearch_dashboards.json create mode 100644 plugins/wazuh-endpoints/package.json create mode 100644 plugins/wazuh-endpoints/public/index.ts create mode 100644 plugins/wazuh-endpoints/public/plugin.ts create mode 100644 plugins/wazuh-endpoints/public/types.ts create mode 100644 plugins/wazuh-endpoints/scripts/jest.js create mode 100644 plugins/wazuh-endpoints/scripts/manifest.js create mode 100755 plugins/wazuh-endpoints/scripts/runner.js create mode 100644 plugins/wazuh-endpoints/server/index.ts create mode 100644 plugins/wazuh-endpoints/server/plugin.ts create mode 100644 plugins/wazuh-endpoints/server/types.ts create mode 100644 plugins/wazuh-endpoints/test/jest/config.js create mode 100644 plugins/wazuh-endpoints/translations/en-US.json create mode 100644 plugins/wazuh-endpoints/tsconfig.json diff --git a/docker/osd-dev/dev.yml b/docker/osd-dev/dev.yml index 04a7c00266..4e2ac3f17c 100755 --- a/docker/osd-dev/dev.yml +++ b/docker/osd-dev/dev.yml @@ -231,6 +231,7 @@ services: - '${SRC}/main:/home/node/kbn/plugins/wazuh' - '${SRC}/wazuh-core:/home/node/kbn/plugins/wazuh-core' - '${SRC}/wazuh-check-updates:/home/node/kbn/plugins/wazuh-check-updates' + - '${SRC}/wazuh-endpoints:/home/node/kbn/plugins/wazuh-endpoints' - wd_certs:/home/node/kbn/certs/ - ${WAZUH_DASHBOARD_CONF}:/home/node/kbn/config/opensearch_dashboards.yml - ./config/${OSD_MAJOR}/osd/wazuh.yml:/home/node/kbn/data/wazuh/config/wazuh.yml diff --git a/plugins/main/opensearch_dashboards.json b/plugins/main/opensearch_dashboards.json index ce02138f9d..c22c97b0b7 100644 --- a/plugins/main/opensearch_dashboards.json +++ b/plugins/main/opensearch_dashboards.json @@ -2,9 +2,7 @@ "id": "wazuh", "version": "4.9.0-00", "opensearchDashboardsVersion": "opensearchDashboards", - "configPath": [ - "wazuh" - ], + "configPath": ["wazuh"], "requiredPlugins": [ "navigation", "data", @@ -19,7 +17,8 @@ "opensearchDashboardsReact", "opensearchDashboardsUtils", "opensearchDashboardsLegacy", - "wazuhCheckUpdates" + "wazuhCheckUpdates", + "wazuhEndpoints" ], "optionalPlugins": [ "security", @@ -29,4 +28,4 @@ ], "server": true, "ui": true -} \ No newline at end of file +} diff --git a/plugins/main/public/components/common/welcome/overview-welcome.js b/plugins/main/public/components/common/welcome/overview-welcome.js index 4ae352cc7c..42afc33bbf 100644 --- a/plugins/main/public/components/common/welcome/overview-welcome.js +++ b/plugins/main/public/components/common/welcome/overview-welcome.js @@ -22,7 +22,6 @@ import { EuiFlexGrid, EuiCallOut, EuiPage, - EuiButtonEmpty, } from '@elastic/eui'; import './welcome.scss'; import { @@ -39,6 +38,7 @@ import { } from '../../../utils/applications'; import { getCore } from '../../../kibana-services'; import { RedirectAppLinks } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; +import { WzButtonPermissions } from '../../common/permissions/button'; const appCategories = Applications.reduce((categories, app) => { const existingCategory = categories.find( @@ -85,14 +85,21 @@ export const OverviewWelcome = compose( title={ <> No agents were added to this manager.{' '} - - Add agent - + Deploy new agent + } color='warning' diff --git a/plugins/main/public/controllers/agent/components/agents-preview.scss b/plugins/main/public/components/endpoints-summary/endpoints-summary.scss similarity index 100% rename from plugins/main/public/controllers/agent/components/agents-preview.scss rename to plugins/main/public/components/endpoints-summary/endpoints-summary.scss diff --git a/plugins/main/public/components/endpoints-summary/endpoints-summary.tsx b/plugins/main/public/components/endpoints-summary/endpoints-summary.tsx new file mode 100644 index 0000000000..28f6e36eb7 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/endpoints-summary.tsx @@ -0,0 +1,332 @@ +/* + * Wazuh app - React component for building the agents preview section. + * + * Copyright (C) 2015-2022 Wazuh, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Find more information about this on the LICENSE file. + */ + +import React, { Component } from 'react'; +import { + EuiPage, + EuiFlexGroup, + EuiFlexItem, + EuiStat, + EuiSpacer, + EuiToolTip, + EuiCard, + EuiLink, + EuiText, +} 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 { + withReduxProvider, + withGlobalBreadcrumb, + withUserAuthorizationPrompt, + withErrorBoundary, +} from '../common/hocs'; +import { formatUIDate } from '../../../public/react-services/time-service'; +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 { endpointSumary, itHygiene } from '../../utils/applications'; +import { ShareAgent } from '../../factories/share-agent'; +import { getCore } from '../../kibana-services'; +import './endpoints-summary.scss'; + +export const EndpointsSummary = compose( + withErrorBoundary, + withReduxProvider, + withGlobalBreadcrumb([{ text: endpointSumary.title }]), + withUserAuthorizationPrompt([ + [ + { action: 'agent:read', resource: 'agent:id:*' }, + { action: 'agent:read', resource: 'agent:group:*' }, + ], + ]), +)( + class EndpointsSummary extends Component { + _isMount = false; + constructor() { + super(); + this.state = { + loadingSummary: true, + loadingLastRegisteredAgent: true, + agentTableFilters: [], + agentStatusSummary: [], + agentsActiveCoverage: undefined, + }; + this.wazuhConfig = new WazuhConfig(); + this.agentStatus = UI_ORDER_AGENT_STATUS.map(agentStatus => ({ + status: agentStatus, + label: agentStatusLabelByAgentStatus(agentStatus), + color: agentStatusColorByAgentStatus(agentStatus), + })); + this.shareAgent = new ShareAgent(); + } + + async componentDidMount() { + this._isMount = true; + this.getSummary(); + this.fetchLastRegisteredAgent(); + if (this.wazuhConfig.getConfig()['wazuh.monitoring.enabled']) { + const tabVisualizations = new TabVisualizations(); + tabVisualizations.removeAll(); + tabVisualizations.setTab('general'); + tabVisualizations.assign({ + general: 1, + }); + const filterHandler = new FilterHandler(AppState.getCurrentPattern()); + await VisFactoryHandler.buildOverviewVisualizations( + filterHandler, + 'general', + null, + ); + } + } + + componentWillUnmount() { + this._isMount = false; + } + + groupBy = function (arr) { + return arr.reduce(function (prev, item) { + if (item in prev) prev[item]++; + else prev[item] = 1; + return prev; + }, {}); + }; + + 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) { + this.setState({ + loadingSummary: false, + agentStatusSummary: [], + agentsActiveCoverage: undefined, + }); + 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' }, + }); + this.setState({ + loadingLastRegisteredAgent: false, + lastRegisteredAgent, + }); + } catch (error) { + this.setState({ + loadingLastRegisteredAgent: false, + }); + 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); + } + } + + removeFilters() { + this._isMount && this.setState({ agentTableFilters: {} }); + } + + filterAgentByStatus(status) { + this._isMount && + this.setState({ + agentTableFilters: { q: `id!=000;status=${status}` }, + }); + } + + render() { + 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?.id ? ( + + {this.state.lastRegisteredAgent.name || '-'} + + ) : ( + - + )} + + } + titleSize='s' + description='Last registered agent' + titleColor='primary' + /> + + + + + + + + this.removeFilters()} + formatUIDate={date => formatUIDate(date)} + /> + + + + ); + } + }, +); diff --git a/plugins/main/public/components/endpoints-summary/hooks/endpoints.ts b/plugins/main/public/components/endpoints-summary/hooks/endpoints.ts new file mode 100644 index 0000000000..0a84de9291 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/hooks/endpoints.ts @@ -0,0 +1,37 @@ +import { useState, useEffect } from 'react'; +import { WzRequest } from '../../../react-services/wz-request'; + +export const useGetTotalEndpoints = () => { + const [totalEndpoints, setTotalEndpoints] = useState(); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(); + + const getTotalEndpoints = async () => { + try { + setIsLoading(true); + const { + data: { + data: { total_affected_items }, + }, + } = await WzRequest.apiReq('GET', '/agents', { + params: { limit: 1, q: 'id!=000' }, + }); + setTotalEndpoints(total_affected_items); + setError(undefined); + } catch (error: any) { + setError(error); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + getTotalEndpoints(); + }, []); + + return { + isLoading, + totalEndpoints, + error, + }; +}; diff --git a/plugins/main/public/components/endpoints-summary/hooks/index.ts b/plugins/main/public/components/endpoints-summary/hooks/index.ts new file mode 100644 index 0000000000..c63b479ae4 --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/hooks/index.ts @@ -0,0 +1 @@ +export { useGetTotalEndpoints } from './endpoints'; diff --git a/plugins/main/public/components/endpoints-summary/index.tsx b/plugins/main/public/components/endpoints-summary/index.tsx new file mode 100644 index 0000000000..045ff0a14c --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/index.tsx @@ -0,0 +1,86 @@ +import React, { useState, useEffect } from 'react'; +import { + EuiPage, + EuiPageBody, + EuiEmptyPrompt, + EuiProgress, +} from '@elastic/eui'; +import { EndpointsSummary } from './endpoints-summary'; +import { WzRequest } from '../../react-services/wz-request'; +import { endpointSumary } from '../../utils/applications'; +import { + withErrorBoundary, + withReduxProvider, + withGlobalBreadcrumb, +} from '../common/hocs'; +import { compose } from 'redux'; +import { WzButtonPermissions } from '../common/permissions/button'; +import { getCore } from '../../kibana-services'; +import { UI_LOGGER_LEVELS } from '../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; +import { getErrorOrchestrator } from '../../react-services/common-services'; +import { useGetTotalEndpoints } from './hooks'; + +export const MainEndpointsSummary = compose( + withErrorBoundary, + withReduxProvider, + withGlobalBreadcrumb([{ text: endpointSumary.title }]), +)(() => { + const { isLoading, totalEndpoints, error } = useGetTotalEndpoints(); + + if (error) { + const options = { + context: `MainEndpointsSummary.getTotalEndpoints`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.CRITICAL, + store: true, + error: { + error: error, + message: error.message || error, + title: `Could not get agents summary`, + }, + }; + getErrorOrchestrator().handleError(options); + } + + if (isLoading) { + return ( + + + + + + ); + } + + if (totalEndpoints === 0) { + return ( + No agents were added to this manager.} + body={

Add agents to fleet to start monitoring

} + actions={ + + Deploy new agent + + } + /> + ); + } + + return ( + + + + + + ); +}); diff --git a/plugins/main/public/controllers/register-agent/components/command-output/command-output.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/command-output/command-output.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/components/command-output/command-output.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/command-output/command-output.tsx diff --git a/plugins/main/public/controllers/register-agent/components/group-input/group-input.scss b/plugins/main/public/components/endpoints-summary/register-agent/components/group-input/group-input.scss similarity index 100% rename from plugins/main/public/controllers/register-agent/components/group-input/group-input.scss rename to plugins/main/public/components/endpoints-summary/register-agent/components/group-input/group-input.scss diff --git a/plugins/main/public/controllers/register-agent/components/group-input/group-input.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/group-input/group-input.tsx similarity index 94% rename from plugins/main/public/controllers/register-agent/components/group-input/group-input.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/group-input/group-input.tsx index e12c301850..3a4d7e3b45 100644 --- a/plugins/main/public/controllers/register-agent/components/group-input/group-input.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/components/group-input/group-input.tsx @@ -8,8 +8,8 @@ import { EuiButtonEmpty, EuiLink, } from '@elastic/eui'; -import { webDocumentationLink } from '../../../../../common/services/web_documentation'; -import { PLUGIN_VERSION_SHORT } from '../../../../../common/constants'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +import { PLUGIN_VERSION_SHORT } from '../../../../../../common/constants'; import './group-input.scss'; const popoverAgentGroup = ( diff --git a/plugins/main/public/controllers/register-agent/components/optionals-inputs/optionals-inputs.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/optionals-inputs/optionals-inputs.tsx similarity index 85% rename from plugins/main/public/controllers/register-agent/components/optionals-inputs/optionals-inputs.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/optionals-inputs/optionals-inputs.tsx index 317e3b6c41..89812bb617 100644 --- a/plugins/main/public/controllers/register-agent/components/optionals-inputs/optionals-inputs.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/components/optionals-inputs/optionals-inputs.tsx @@ -1,5 +1,5 @@ import React, { Fragment, useState } from 'react'; -import { UseFormReturn } from '../../../../components/common/form/types'; +import { UseFormReturn } from '../../../../common/form/types'; import { EuiFlexGroup, EuiFlexItem, @@ -9,10 +9,10 @@ import { EuiCallOut, EuiLink, } from '@elastic/eui'; -import { InputForm } from '../../../../components/common/form'; +import { InputForm } from '../../../../common/form'; import { OPTIONAL_PARAMETERS_TEXT } from '../../utils/register-agent-data'; -import { webDocumentationLink } from '../../../../../common/services/web_documentation'; -import { PLUGIN_VERSION_SHORT } from '../../../../../common/constants'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +import { PLUGIN_VERSION_SHORT } from '../../../../../../common/constants'; import '../group-input/group-input.scss'; interface OptionalsInputsProps { formFields: UseFormReturn['fields']; @@ -27,7 +27,7 @@ const OptionalsInputs = (props: OptionalsInputsProps) => { const agentNameDocLink = webDocumentationLink( 'user-manual/reference/ossec-conf/client.html#enrollment-agent-name', PLUGIN_VERSION_SHORT, - ) + ); const popoverAgentName = ( Learn about{' '} @@ -94,11 +94,16 @@ const OptionalsInputs = (props: OptionalsInputsProps) => { /> {warningForAgentName}} + title={ + + {warningForAgentName} + + + } iconType='iInCircle' className='warningForAgentName' /> diff --git a/plugins/main/public/controllers/register-agent/components/os-selector/checkbox-group/checkbox-group.scss b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/checkbox-group/checkbox-group.scss similarity index 100% rename from plugins/main/public/controllers/register-agent/components/os-selector/checkbox-group/checkbox-group.scss rename to plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/checkbox-group/checkbox-group.scss diff --git a/plugins/main/public/controllers/register-agent/components/os-selector/checkbox-group/checkbox-group.test.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/checkbox-group/checkbox-group.test.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/components/os-selector/checkbox-group/checkbox-group.test.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/checkbox-group/checkbox-group.test.tsx diff --git a/plugins/main/public/controllers/register-agent/components/os-selector/checkbox-group/checkbox-group.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/checkbox-group/checkbox-group.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/components/os-selector/checkbox-group/checkbox-group.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/checkbox-group/checkbox-group.tsx diff --git a/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.scss b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.scss similarity index 100% rename from plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.scss rename to plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.scss diff --git a/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.test.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.test.tsx similarity index 82% rename from plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.test.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.test.tsx index d01a27c141..dea9040ed0 100644 --- a/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.test.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.test.tsx @@ -1,16 +1,16 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { OsCard } from '../os-card/os-card'; +import { OsCard } from './os-card'; -jest.mock('../../../../../kibana-services', () => ({ - ...(jest.requireActual('../../../../../kibana-services') as object), +jest.mock('../../../../../../kibana-services', () => ({ + ...(jest.requireActual('../../../../../../kibana-services') as object), getHttp: jest.fn().mockReturnValue({ basePath: { get: () => { return 'http://localhost:5601'; }, - prepend: (url) => { + prepend: url => { return `http://localhost:5601${url}`; }, }, @@ -27,7 +27,7 @@ jest.mock('../../../../../kibana-services', () => ({ }, }), getUiSettings: jest.fn().mockReturnValue({ - get: (name) => { + get: name => { return true; }, }), diff --git a/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.tsx similarity index 95% rename from plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.tsx index 1e88422e5b..7aa8c633c4 100644 --- a/plugins/main/public/controllers/register-agent/components/os-selector/os-card/os-card.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/components/os-selector/os-card/os-card.tsx @@ -10,7 +10,7 @@ import { import { OPERATING_SYSTEMS_OPTIONS } from '../../../utils/register-agent-data'; import { CheckboxGroupComponent } from '../checkbox-group/checkbox-group'; import './os-card.scss'; -import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +import { webDocumentationLink } from '../../../../../../../common/services/web_documentation'; interface Props { setStatusCheck: string; diff --git a/plugins/main/public/controllers/register-agent/components/server-address/server-address.tsx b/plugins/main/public/components/endpoints-summary/register-agent/components/server-address/server-address.tsx similarity index 76% rename from plugins/main/public/controllers/register-agent/components/server-address/server-address.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/components/server-address/server-address.tsx index 33faea533a..02b9cc8d1f 100644 --- a/plugins/main/public/controllers/register-agent/components/server-address/server-address.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/components/server-address/server-address.tsx @@ -5,18 +5,17 @@ import { EuiPopover, EuiButtonEmpty, EuiLink, - EuiSwitch + EuiSwitch, } from '@elastic/eui'; import React, { Fragment, useEffect, useState } from 'react'; import { SERVER_ADDRESS_TEXTS } from '../../utils/register-agent-data'; -import { EnhancedFieldConfiguration } from '../../../../components/common/form/types'; -import { InputForm } from '../../../../components/common/form'; -import { webDocumentationLink } from '../../../../../common/services/web_documentation'; -import { PLUGIN_VERSION_SHORT } from '../../../../../common/constants'; +import { EnhancedFieldConfiguration } from '../../../../common/form/types'; +import { InputForm } from '../../../../common/form'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; +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 { WzRequest } from '../../../../../react-services'; +import { ErrorHandler } from '../../../../../react-services/error-management/error-handler/error-handler'; interface ServerAddressInputProps { formField: EnhancedFieldConfiguration; @@ -47,49 +46,47 @@ const ServerAddressInput = (props: ServerAddressInputProps) => { ); const closeServerAddress = () => setIsPopoverServerAddress(false); const [rememberServerAddress, setRememberServerAddress] = useState(false); - const [defaultServerAddress, setDefaultServerAddress] = useState(formField?.initialValue ? formField?.initialValue : '') + const [defaultServerAddress, setDefaultServerAddress] = useState( + formField?.initialValue ? formField?.initialValue : '', + ); - const handleToggleRememberAddress = async (event) => { + const handleToggleRememberAddress = async event => { setRememberServerAddress(event.target.checked); - if(event.target.checked){ + if (event.target.checked) { await saveServerAddress(); - setDefaultServerAddress(formField.value) + setDefaultServerAddress(formField.value); } }; const saveServerAddress = async () => { - try{ - const res = await WzRequest.genericReq( - 'PUT', '/utils/configuration', - { - 'enrollment.dns': formField.value, - } - ) - }catch(error){ + try { + const res = await WzRequest.genericReq('PUT', '/utils/configuration', { + 'enrollment.dns': formField.value, + }); + } catch (error) { ErrorHandler.handleError(error, { message: error.message, - title: 'Error saving server address configuration' - }) + title: 'Error saving server address configuration', + }); setRememberServerAddress(false); } - } + }; const rememberToggleIsDisabled = () => { return !formField.value || !!formField.error; - } + }; - const handleInputChange = (value) => { - if(value === defaultServerAddress){ + const handleInputChange = value => { + if (value === defaultServerAddress) { setRememberServerAddress(true); - }else{ + } else { setRememberServerAddress(false); } - - } + }; useEffect(() => { - handleInputChange(formField.value) - }, [formField.value]) + handleInputChange(formField.value); + }, [formField.value]); return ( @@ -152,12 +149,11 @@ const ServerAddressInput = (props: ServerAddressInputProps) => { handleToggleRememberAddress(e)} + onChange={e => handleToggleRememberAddress(e)} /> - ); diff --git a/plugins/main/public/controllers/register-agent/containers/register-agent/register-agent.scss b/plugins/main/public/components/endpoints-summary/register-agent/containers/register-agent/register-agent.scss similarity index 100% rename from plugins/main/public/controllers/register-agent/containers/register-agent/register-agent.scss rename to plugins/main/public/components/endpoints-summary/register-agent/containers/register-agent/register-agent.scss diff --git a/plugins/main/public/components/endpoints-summary/register-agent/containers/register-agent/register-agent.tsx b/plugins/main/public/components/endpoints-summary/register-agent/containers/register-agent/register-agent.tsx new file mode 100644 index 0000000000..b973ec5b1e --- /dev/null +++ b/plugins/main/public/components/endpoints-summary/register-agent/containers/register-agent/register-agent.tsx @@ -0,0 +1,254 @@ +import React, { useState, useEffect } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiTitle, + EuiButtonEmpty, + EuiPage, + EuiPageBody, + EuiSpacer, + EuiProgress, + EuiButton, +} from '@elastic/eui'; +import { WzRequest } from '../../../../../react-services/wz-request'; +import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; +import { UI_ERROR_SEVERITIES } from '../../../../../react-services/error-orchestrator/types'; +import { ErrorHandler } from '../../../../../react-services/error-management'; +import './register-agent.scss'; +import { Steps } from '../steps/steps'; +import { InputForm } from '../../../../common/form'; +import { + getGroups, + getMasterConfiguration, +} from '../../services/register-agent-services'; +import { useForm } from '../../../../common/form/hooks'; +import { FormConfiguration } from '../../../../common/form/types'; +import { useSelector } from 'react-redux'; +import { + withErrorBoundary, + withReduxProvider, + withGlobalBreadcrumb, + withUserAuthorizationPrompt, +} from '../../../../common/hocs'; +import GroupInput from '../../components/group-input/group-input'; +import { OsCard } from '../../components/os-selector/os-card/os-card'; +import { + validateServerAddress, + validateAgentName, +} from '../../utils/validations'; +import { compose } from 'redux'; +import { endpointSumary } from '../../../../../utils/applications'; +import { getCore } from '../../../../../kibana-services'; +import { getErrorOrchestrator } from '../../../../../react-services/common-services'; + +export const RegisterAgent = compose( + withErrorBoundary, + withReduxProvider, + withGlobalBreadcrumb([ + { text: endpointSumary.title, href: `#${endpointSumary.redirectTo()}` }, + { text: 'Deploy new agent' }, + ]), + withUserAuthorizationPrompt([ + [{ action: 'agent:create', resource: '*:*:*' }], + ]), +)(() => { + const configuration = useSelector( + (state: { appConfig: { data: any } }) => state.appConfig.data, + ); + const [wazuhVersion, setWazuhVersion] = useState(''); + const [haveUdpProtocol, setHaveUdpProtocol] = useState(false); + const [loading, setLoading] = useState(false); + const [wazuhPassword, setWazuhPassword] = useState(''); + const [groups, setGroups] = useState([]); + const [needsPassword, setNeedsPassword] = useState(false); + + const initialFields: FormConfiguration = { + operatingSystemSelection: { + type: 'custom', + initialValue: '', + component: props => { + return ; + }, + options: { + groups, + }, + }, + serverAddress: { + type: 'text', + initialValue: configuration['enrollment.dns'] || '', + validate: validateServerAddress, + }, + agentName: { + type: 'text', + initialValue: '', + validate: validateAgentName, + }, + + agentGroups: { + type: 'custom', + initialValue: [], + component: props => { + return ; + }, + options: { + groups, + }, + }, + }; + + const form = useForm(initialFields); + + const getMasterConfig = async () => { + const masterConfig = await getMasterConfiguration(); + if (masterConfig?.remote) { + setHaveUdpProtocol(masterConfig.remote.isUdp); + } + return masterConfig; + }; + + const getWazuhVersion = async () => { + try { + const result = await WzRequest.apiReq('GET', '/', {}); + return result?.data?.data?.api_version; + } catch (error) { + const options = { + context: `RegisterAgent.getWazuhVersion`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + error: { + error: error, + message: error.message || error, + title: `Could not get the Wazuh version: ${error.message || error}`, + }, + }; + getErrorOrchestrator().handleError(options); + return version; + } + }; + + useEffect(() => { + const fetchData = async () => { + try { + const wazuhVersion = await getWazuhVersion(); + const { auth: authConfig } = await getMasterConfig(); + // get wazuh password configuration + let wazuhPassword = ''; + const needsPassword = authConfig?.auth?.use_password === 'yes'; + if (needsPassword) { + wazuhPassword = + configuration?.['enrollment.password'] || + authConfig?.['authd.pass'] || + ''; + } + const groups = await getGroups(); + setNeedsPassword(needsPassword); + setWazuhPassword(wazuhPassword); + setWazuhVersion(wazuhVersion); + setGroups(groups); + setLoading(false); + } catch (error) { + setWazuhVersion(wazuhVersion); + setLoading(false); + const options = { + context: 'RegisterAgent', + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + display: true, + store: false, + error: { + error: error, + message: error.message || error, + title: error.name || error, + }, + }; + ErrorHandler.handleError(error, options); + } + }; + + fetchData(); + }, []); + + const osCard = ( + + ); + + return ( +
+ + + + + +
+ + Close + +
+ + + +

+ Deploy new agent +

+
+
+
+ + {loading ? ( + <> + + + + + + ) : ( + + + + )} + + + + Close + + + +
+
+
+
+
+
+ ); +}); diff --git a/plugins/main/public/controllers/register-agent/containers/steps/steps.scss b/plugins/main/public/components/endpoints-summary/register-agent/containers/steps/steps.scss similarity index 100% rename from plugins/main/public/controllers/register-agent/containers/steps/steps.scss rename to plugins/main/public/components/endpoints-summary/register-agent/containers/steps/steps.scss diff --git a/plugins/main/public/controllers/register-agent/containers/steps/steps.tsx b/plugins/main/public/components/endpoints-summary/register-agent/containers/steps/steps.tsx similarity index 97% rename from plugins/main/public/controllers/register-agent/containers/steps/steps.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/containers/steps/steps.tsx index 2c0dec80e2..775a275023 100644 --- a/plugins/main/public/controllers/register-agent/containers/steps/steps.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/containers/steps/steps.tsx @@ -1,5 +1,5 @@ import React, { Fragment, useEffect, useState } from 'react'; -import { EuiCallOut, EuiLink, EuiSteps, EuiTitle } from '@elastic/eui'; +import { EuiCallOut, EuiLink, EuiSteps } from '@elastic/eui'; import './steps.scss'; import { OPERATING_SYSTEMS_OPTIONS } from '../../utils/register-agent-data'; import { @@ -15,7 +15,7 @@ import { tOperatingSystem, tOptionalParameters, } from '../../core/config/os-commands-definitions'; -import { UseFormReturn } from '../../../../components/common/form/types'; +import { UseFormReturn } from '../../../../common/form/types'; import CommandOutput from '../../components/command-output/command-output'; import ServerAddress from '../../components/server-address/server-address'; import OptionalsInputs from '../../components/optionals-inputs/optionals-inputs'; @@ -32,7 +32,7 @@ import { tFormFieldsLabel, tFormStepsLabel, } from '../../services/register-agent-steps-status-services'; -import { webDocumentationLink } from '../../../../../common/services/web_documentation'; +import { webDocumentationLink } from '../../../../../../common/services/web_documentation'; interface IStepsProps { needsPassword: boolean; diff --git a/plugins/main/public/controllers/register-agent/core/config/os-commands-definitions.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/config/os-commands-definitions.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/config/os-commands-definitions.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/config/os-commands-definitions.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/README.md b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/README.md similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/README.md rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/README.md diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/command-generator/command-generator.test.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/command-generator/command-generator.test.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/command-generator/command-generator.test.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/command-generator/command-generator.test.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/command-generator/command-generator.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/command-generator/command-generator.ts similarity index 98% rename from plugins/main/public/controllers/register-agent/core/register-commands/command-generator/command-generator.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/command-generator/command-generator.ts index 3cec8b9772..8194b88970 100644 --- a/plugins/main/public/controllers/register-agent/core/register-commands/command-generator/command-generator.ts +++ b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/command-generator/command-generator.ts @@ -19,7 +19,7 @@ import { NoOSSelectedException, WazuhVersionUndefinedException, } from '../exceptions'; -import { version } from '../../../../../../package.json'; +import { version } from '../../../../../../../package.json'; export class CommandGenerator< OS extends IOperationSystem, diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/exceptions/index.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/exceptions/index.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/exceptions/index.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/exceptions/index.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.test.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.test.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.test.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.test.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/optional-parameters-manager/optional-parameters-manager.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/services/get-install-command.service.test.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/get-install-command.service.test.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/services/get-install-command.service.test.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/get-install-command.service.test.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/services/get-install-command.service.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/get-install-command.service.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/services/get-install-command.service.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/get-install-command.service.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/services/search-os-definitions.service.test.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/search-os-definitions.service.test.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/services/search-os-definitions.service.test.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/search-os-definitions.service.test.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/services/search-os-definitions.service.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/search-os-definitions.service.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/services/search-os-definitions.service.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/services/search-os-definitions.service.ts diff --git a/plugins/main/public/controllers/register-agent/core/register-commands/types.ts b/plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/types.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/core/register-commands/types.ts rename to plugins/main/public/components/endpoints-summary/register-agent/core/register-commands/types.ts diff --git a/plugins/main/public/controllers/register-agent/hooks/README.md b/plugins/main/public/components/endpoints-summary/register-agent/hooks/README.md similarity index 100% rename from plugins/main/public/controllers/register-agent/hooks/README.md rename to plugins/main/public/components/endpoints-summary/register-agent/hooks/README.md diff --git a/plugins/main/public/controllers/register-agent/hooks/use-register-agent-commands.test.ts b/plugins/main/public/components/endpoints-summary/register-agent/hooks/use-register-agent-commands.test.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/hooks/use-register-agent-commands.test.ts rename to plugins/main/public/components/endpoints-summary/register-agent/hooks/use-register-agent-commands.test.ts diff --git a/plugins/main/public/controllers/register-agent/hooks/use-register-agent-commands.ts b/plugins/main/public/components/endpoints-summary/register-agent/hooks/use-register-agent-commands.ts similarity index 98% rename from plugins/main/public/controllers/register-agent/hooks/use-register-agent-commands.ts rename to plugins/main/public/components/endpoints-summary/register-agent/hooks/use-register-agent-commands.ts index 80c3db0530..414f2ea41f 100644 --- a/plugins/main/public/controllers/register-agent/hooks/use-register-agent-commands.ts +++ b/plugins/main/public/components/endpoints-summary/register-agent/hooks/use-register-agent-commands.ts @@ -6,7 +6,7 @@ import { IOptionalParameters, tOptionalParams, } from '../core/register-commands/types'; -import { version } from '../../../../package.json'; +import { version } from '../../../../../package.json'; interface IUseRegisterCommandsProps< OS extends IOperationSystem, diff --git a/plugins/main/public/controllers/register-agent/index.tsx b/plugins/main/public/components/endpoints-summary/register-agent/index.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/index.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/index.tsx diff --git a/plugins/main/public/controllers/register-agent/interfaces/types.ts b/plugins/main/public/components/endpoints-summary/register-agent/interfaces/types.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/interfaces/types.ts rename to plugins/main/public/components/endpoints-summary/register-agent/interfaces/types.ts diff --git a/plugins/main/public/controllers/register-agent/services/form-status-manager.test.tsx b/plugins/main/public/components/endpoints-summary/register-agent/services/form-status-manager.test.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/services/form-status-manager.test.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/services/form-status-manager.test.tsx diff --git a/plugins/main/public/controllers/register-agent/services/form-status-manager.tsx b/plugins/main/public/components/endpoints-summary/register-agent/services/form-status-manager.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/services/form-status-manager.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/services/form-status-manager.tsx diff --git a/plugins/main/public/controllers/register-agent/services/register-agent-os-commands-services.tsx b/plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-os-commands-services.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/services/register-agent-os-commands-services.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-os-commands-services.tsx diff --git a/plugins/main/public/controllers/agent/components/register-agent-service.test.ts b/plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-services.test.ts similarity index 84% rename from plugins/main/public/controllers/agent/components/register-agent-service.test.ts rename to plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-services.test.ts index e61c0bc57a..ecc68fb6b9 100644 --- a/plugins/main/public/controllers/agent/components/register-agent-service.test.ts +++ b/plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-services.test.ts @@ -1,24 +1,14 @@ -import * as RegisterAgentService from './register-agent-service'; -import { WzRequest } from '../../../react-services/wz-request'; -import { ServerAddressOptions } from '../register-agent/steps'; +import * as RegisterAgentService from './register-agent-services'; +import { WzRequest } from '../../../../react-services/wz-request'; +import { ServerAddressOptions } from './register-agent-services'; -jest.mock('../../../react-services', () => ({ - ...(jest.requireActual('../../../react-services') as object), +jest.mock('../../../../react-services', () => ({ + ...(jest.requireActual('../../../../react-services') as object), WzRequest: () => ({ apiReq: jest.fn(), }), })); -const mockedResponseClusterStatus = { - data: { - data: { - enabled: 'yes', - running: 'yes', - }, - error: 0, - }, -}; - describe('Register agent service', () => { beforeEach(() => jest.clearAllMocks()); describe('getRemoteConfiguration', () => { @@ -51,13 +41,11 @@ describe('Register agent service', () => { }, }; - WzRequest.apiReq = jest - .fn() - .mockResolvedValueOnce(mockedResponseClusterStatus) - .mockResolvedValueOnce(mockedResponse); + WzRequest.apiReq = jest.fn().mockResolvedValueOnce(mockedResponse); const nodeName = 'example-node'; const res = await RegisterAgentService.getRemoteConfiguration( - 'example-node', + nodeName, + false, ); expect(res.name).toBe(nodeName); expect(res.haveSecureConnection).toBe(true); @@ -84,13 +72,11 @@ describe('Register agent service', () => { }, }, }; - WzRequest.apiReq = jest - .fn() - .mockResolvedValueOnce(mockedResponseClusterStatus) - .mockResolvedValueOnce(mockedResponse); + WzRequest.apiReq = jest.fn().mockResolvedValueOnce(mockedResponse); const nodeName = 'example-node'; const res = await RegisterAgentService.getRemoteConfiguration( - 'example-node', + nodeName, + false, ); expect(res.name).toBe(nodeName); expect(res.haveSecureConnection).toBe(false); @@ -124,13 +110,11 @@ describe('Register agent service', () => { }, }, }; - WzRequest.apiReq = jest - .fn() - .mockResolvedValueOnce(mockedResponseClusterStatus) - .mockResolvedValueOnce(mockedResponse); + WzRequest.apiReq = jest.fn().mockResolvedValueOnce(mockedResponse); const nodeName = 'example-node'; const res = await RegisterAgentService.getRemoteConfiguration( - 'example-node', + nodeName, + false, ); expect(res.name).toBe(nodeName); expect(res.isUdp).toEqual(true); @@ -164,13 +148,11 @@ describe('Register agent service', () => { }, }, }; - WzRequest.apiReq = jest - .fn() - .mockResolvedValueOnce(mockedResponseClusterStatus) - .mockResolvedValueOnce(mockedResponse); + WzRequest.apiReq = jest.fn().mockResolvedValueOnce(mockedResponse); const nodeName = 'example-node'; const res = await RegisterAgentService.getRemoteConfiguration( - 'example-node', + nodeName, + false, ); expect(res.name).toBe(nodeName); expect(res.isUdp).toEqual(false); @@ -204,13 +186,11 @@ describe('Register agent service', () => { }, }, }; - WzRequest.apiReq = jest - .fn() - .mockResolvedValueOnce(mockedResponseClusterStatus) - .mockResolvedValueOnce(mockedResponse); + WzRequest.apiReq = jest.fn().mockResolvedValueOnce(mockedResponse); const nodeName = 'example-node'; const res = await RegisterAgentService.getRemoteConfiguration( - 'example-node', + nodeName, + false, ); expect(res.name).toBe(nodeName); expect(res.isUdp).toEqual(false); diff --git a/plugins/main/public/controllers/register-agent/services/register-agent-services.tsx b/plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-services.tsx similarity index 77% rename from plugins/main/public/controllers/register-agent/services/register-agent-services.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-services.tsx index 8200224bb2..19de19808e 100644 --- a/plugins/main/public/controllers/register-agent/services/register-agent-services.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-services.tsx @@ -1,5 +1,5 @@ -import { UseFormReturn } from '../../../components/common/form/types'; -import { WzRequest } from '../../../react-services/wz-request'; +import { UseFormReturn } from '../../../../components/common/form/types'; +import { WzRequest } from '../../../../react-services/wz-request'; import { tOperatingSystem, tOptionalParameters, @@ -22,6 +22,12 @@ type RemoteConfig = { haveSecureConnection: boolean | null; }; +export type ServerAddressOptions = { + label: string; + value: string; + nodetype: string; +}; + /** * Get the cluster status */ @@ -42,7 +48,10 @@ export const clusterStatusResponse = async (): Promise => { /** * Get the remote configuration from api */ -async function getRemoteConfiguration(nodeName: string): Promise { +async function getRemoteConfiguration( + nodeName: string, + clusterStatus: boolean, +): Promise { let config: RemoteConfig = { name: nodeName, isUdp: false, @@ -50,7 +59,6 @@ async function getRemoteConfiguration(nodeName: string): Promise { }; try { - const clusterStatus = await clusterStatusResponse(); let result; if (clusterStatus) { result = await WzRequest.apiReq( @@ -65,7 +73,7 @@ async function getRemoteConfiguration(nodeName: string): Promise { {}, ); } - const items = ((result.data || {}).data || {}).affected_items || []; + const items = result?.data?.data?.affected_items || []; const remote = items[0]?.remote; if (remote) { const remoteFiltered = remote.filter((item: RemoteItem) => { @@ -92,6 +100,19 @@ async function getRemoteConfiguration(nodeName: string): Promise { return config; } } +/** + * Get the manager/cluster auth configuration from Wazuh API + * @param node + * @returns + */ +async function getAuthConfiguration(node: string, clusterStatus: boolean) { + const authConfigUrl = clusterStatus + ? `/cluster/${node}/configuration/auth/auth` + : '/manager/configuration/auth/auth'; + const result = await WzRequest.apiReq('GET', authConfigUrl, {}); + const auth = result?.data?.data?.affected_items?.[0]; + return auth; +} /** * Get the remote protocol available from list of protocols @@ -111,14 +132,18 @@ function getRemoteProtocol(protocols: Protocol[]) { * @param defaultServerAddress */ async function getConnectionConfig( - nodeSelected: any, + nodeSelected: ServerAddressOptions, defaultServerAddress?: string, ) { const nodeName = nodeSelected?.label; const nodeIp = nodeSelected?.value; if (!defaultServerAddress) { if (nodeSelected.nodetype !== 'custom') { - const remoteConfig = await getRemoteConfiguration(nodeName); + const clusterStatus = await clusterStatusResponse(); + const remoteConfig = await getRemoteConfiguration( + nodeName, + clusterStatus, + ); return { serverAddress: nodeIp, udpProtocol: remoteConfig.isUdp, @@ -179,7 +204,9 @@ export const getManagerNode = async (): Promise => { * Parse the nodes list from the API response to a format that can be used by the EuiComboBox * @param nodes */ -export const parseNodesInOptions = (nodes: NodeResponse): any[] => { +export const parseNodesInOptions = ( + nodes: NodeResponse, +): ServerAddressOptions[] => { return nodes.data.data.affected_items.map((item: NodeItem) => ({ label: item.name, value: item.ip, @@ -190,7 +217,9 @@ export const parseNodesInOptions = (nodes: NodeResponse): any[] => { /** * Get the list of the cluster nodes from API and parse it into a list of options */ -export const fetchClusterNodesOptions = async (): Promise => { +export const fetchClusterNodesOptions = async (): Promise< + ServerAddressOptions[] +> => { const clusterStatus = await clusterStatusResponse(); if (clusterStatus) { // Cluster mode @@ -208,18 +237,29 @@ export const fetchClusterNodesOptions = async (): Promise => { * Get the master node data from the list of cluster nodes * @param nodeIps */ -export const getMasterNode = (nodeIps: any[]): any[] => { +export const getMasterNode = ( + nodeIps: ServerAddressOptions[], +): ServerAddressOptions[] => { return nodeIps.filter(nodeIp => nodeIp.nodetype === 'master'); }; /** - * Get the remote configuration from manager + * Get the remote and the auth configuration from manager * This function get the config from manager mode or cluster mode */ -export const getMasterRemoteConfiguration = async () => { +export const getMasterConfiguration = async () => { const nodes = await fetchClusterNodesOptions(); const masterNode = getMasterNode(nodes); - return await getRemoteConfiguration(masterNode[0].label); + const clusterStatus = await clusterStatusResponse(); + const remote = await getRemoteConfiguration( + masterNode[0].label, + clusterStatus, + ); + const auth = await getAuthConfiguration(masterNode[0].label, clusterStatus); + return { + remote, + auth, + }; }; export { getConnectionConfig, getRemoteConfiguration }; @@ -260,16 +300,18 @@ export interface IParseRegisterFormValues { export const parseRegisterAgentFormValues = ( formValues: { name: keyof UseFormReturn['fields']; value: any }[], OSOptionsDefined: RegisterAgentData[], - initialValues?: IParseRegisterFormValues + initialValues?: IParseRegisterFormValues, ) => { // return the values form the formFields and the value property - const parsedForm = initialValues || { - operatingSystem: { - architecture: '', - name: '', - }, - optionalParams: {}, - } as IParseRegisterFormValues; + const parsedForm = + initialValues || + ({ + operatingSystem: { + architecture: '', + name: '', + }, + optionalParams: {}, + } as IParseRegisterFormValues); formValues.forEach(field => { if (field.name === 'operatingSystemSelection') { // search the architecture defined in architecture array and get the os name defined in title array in the same index @@ -284,7 +326,9 @@ export const parseRegisterAgentFormValues = ( } } else { if (field.name === 'agentGroups') { - parsedForm.optionalParams[field.name as any] = field.value.map(item => item.id) + parsedForm.optionalParams[field.name as any] = field.value.map( + item => item.id, + ); } else { parsedForm.optionalParams[field.name as any] = field.value; } @@ -292,4 +336,4 @@ export const parseRegisterAgentFormValues = ( }); return parsedForm; -}; \ No newline at end of file +}; diff --git a/plugins/main/public/controllers/register-agent/services/register-agent-steps-status-services.tsx b/plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-steps-status-services.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/services/register-agent-steps-status-services.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/services/register-agent-steps-status-services.tsx diff --git a/plugins/main/public/controllers/register-agent/services/wazuh-password-service.test.ts b/plugins/main/public/components/endpoints-summary/register-agent/services/wazuh-password-service.test.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/services/wazuh-password-service.test.ts rename to plugins/main/public/components/endpoints-summary/register-agent/services/wazuh-password-service.test.ts diff --git a/plugins/main/public/controllers/register-agent/services/wazuh-password-service.ts b/plugins/main/public/components/endpoints-summary/register-agent/services/wazuh-password-service.ts similarity index 100% rename from plugins/main/public/controllers/register-agent/services/wazuh-password-service.ts rename to plugins/main/public/components/endpoints-summary/register-agent/services/wazuh-password-service.ts diff --git a/plugins/main/public/controllers/register-agent/utils/register-agent-data.tsx b/plugins/main/public/components/endpoints-summary/register-agent/utils/register-agent-data.tsx similarity index 64% rename from plugins/main/public/controllers/register-agent/utils/register-agent-data.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/utils/register-agent-data.tsx index 378bf61d33..41f1b8e76e 100644 --- a/plugins/main/public/controllers/register-agent/utils/register-agent-data.tsx +++ b/plugins/main/public/components/endpoints-summary/register-agent/utils/register-agent-data.tsx @@ -1,11 +1,11 @@ import { RegisterAgentData } from '../interfaces/types'; -import LinuxDarkIcon from '../../../../public/assets/images/themes/dark/linux-icon.svg'; -import LinuxLightIcon from '../../../../public/assets/images/themes/light/linux-icon.svg'; -import WindowsDarkIcon from '../../../../public/assets/images/themes/dark/windows-icon.svg'; -import WindowsLightIcon from '../../../../public/assets/images/themes/light/windows-icon.svg'; -import MacDarkIcon from '../../../../public/assets/images/themes/dark/mac-icon.svg'; -import MacLightIcon from '../../../../public/assets/images/themes/light/mac-icon.svg'; -import { getUiSettings } from '../../../kibana-services'; +import LinuxDarkIcon from '../../../../../public/assets/images/themes/dark/linux-icon.svg'; +import LinuxLightIcon from '../../../../../public/assets/images/themes/light/linux-icon.svg'; +import WindowsDarkIcon from '../../../../../public/assets/images/themes/dark/windows-icon.svg'; +import WindowsLightIcon from '../../../../../public/assets/images/themes/light/windows-icon.svg'; +import MacDarkIcon from '../../../../../public/assets/images/themes/dark/mac-icon.svg'; +import MacLightIcon from '../../../../../public/assets/images/themes/light/mac-icon.svg'; +import { getUiSettings } from '../../../../kibana-services'; const darkMode = getUiSettings()?.get('theme:darkMode'); diff --git a/plugins/main/public/controllers/register-agent/utils/validations.test.tsx b/plugins/main/public/components/endpoints-summary/register-agent/utils/validations.test.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/utils/validations.test.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/utils/validations.test.tsx diff --git a/plugins/main/public/controllers/register-agent/utils/validations.tsx b/plugins/main/public/components/endpoints-summary/register-agent/utils/validations.tsx similarity index 100% rename from plugins/main/public/controllers/register-agent/utils/validations.tsx rename to plugins/main/public/components/endpoints-summary/register-agent/utils/validations.tsx diff --git a/plugins/main/public/controllers/agent/components/__snapshots__/agent-table.test.tsx.snap b/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap similarity index 85% rename from plugins/main/public/controllers/agent/components/__snapshots__/agent-table.test.tsx.snap rename to plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap index c13fb6e2cd..4465bb6899 100644 --- a/plugins/main/public/controllers/agent/components/__snapshots__/agent-table.test.tsx.snap +++ b/plugins/main/public/components/endpoints-summary/table/__snapshots__/agents-table.test.tsx.snap @@ -35,9 +35,10 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = `
- +
-
+
-
+
-
+
-
+
+ +
+
-
- -
-
-
- -
-
+ WQL + + +
@@ -211,34 +204,6 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = `
-
- -
- +
-
+
-
+
-
+
-
+
+ +
+
-
- -
-
-
- -
-
+ WQL + + +
@@ -790,38 +748,6 @@ exports[`AgentsTable component Renders correctly to match the snapshot with cust
-
- -
- +
-
+
-
+
-
+
-
+
+ +
+
-
- -
-
-
- -
-
+ WQL + + +
@@ -1334,38 +1253,6 @@ exports[`AgentsTable component Renders correctly to match the snapshot with no p
-
- -
this.props.addingNewAgent()} + href={getCore().application.getUrlForApp(endpointSumary.id, { + path: `#${endpointSumary.redirectTo()}deploy`, + })} > Deploy new agent , @@ -475,17 +471,6 @@ export const AgentsTable = withErrorBoundary( }, }, }} - searchBarProps={{ - buttonsRender: () => ( - this.reloadAgents()} - > - Refresh - - ), - }} saveStateStorage={{ system: 'localStorage', key: 'wz-agents-overview-table', @@ -532,11 +517,3 @@ export const AgentsTable = withErrorBoundary( } }, ); - -AgentsTable.propTypes = { - wzReq: PropTypes.func, - addingNewAgent: PropTypes.func, - downloadCsv: PropTypes.func, - timeService: PropTypes.func, - reload: PropTypes.func, -}; diff --git a/plugins/main/public/controllers/agent/components/agent-table.test.tsx b/plugins/main/public/components/endpoints-summary/table/agents-table.test.tsx similarity index 98% rename from plugins/main/public/controllers/agent/components/agent-table.test.tsx rename to plugins/main/public/components/endpoints-summary/table/agents-table.test.tsx index 66ca6e2932..872773a878 100644 --- a/plugins/main/public/controllers/agent/components/agent-table.test.tsx +++ b/plugins/main/public/components/endpoints-summary/table/agents-table.test.tsx @@ -275,6 +275,15 @@ const localStorageMock = (function () { Object.defineProperty(window, 'localStorage', { value: localStorageMock }); +jest.mock('../../../kibana-services', () => ({ + getCore: jest.fn().mockReturnValue({ + application: { + navigateToApp: () => 'http://url', + getUrlForApp: () => 'http://url', + }, + }), +})); + jest.mock('../../../react-services/common-services', () => ({ getErrorOrchestrator: () => ({ handleError: options => {}, diff --git a/plugins/main/public/controllers/agent/agents-preview.js b/plugins/main/public/controllers/agent/agents-preview.js deleted file mode 100644 index 6fd90cce6e..0000000000 --- a/plugins/main/public/controllers/agent/agents-preview.js +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Wazuh app - Agents preview controller - * 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 * as FileSaver from '../../services/file-saver'; -import { DataFactory } from '../../services/data-factory'; -import { version } from '../../../package.json'; -import { clickAction } from '../../services/click-action'; -import { AppState } from '../../react-services/app-state'; -import { WazuhConfig } from '../../react-services/wazuh-config'; -import { GenericRequest } from '../../react-services/generic-request'; -import { WzRequest } from '../../react-services/wz-request'; -import { ShareAgent } from '../../factories/share-agent'; -import { formatUIDate } from '../../react-services/time-service'; -import { ErrorHandler } from '../../react-services/error-handler'; -import { getDataPlugin, getToasts } from '../../kibana-services'; -import store from '../../redux/store'; -import { UI_LOGGER_LEVELS } from '../../../common/constants'; -import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; -import { getErrorOrchestrator } from '../../react-services/common-services'; -import { webDocumentationLink } from '../../../common/services/web_documentation'; - -export class AgentsPreviewController { - /** - * Class constructor - * @param {Object} $scope - * @param {Object} $location - * @param {Object} errorHandler - * @param {Object} csvReq - */ - constructor( - $scope, - $location, - $route, - errorHandler, - csvReq, - commonData, - $window, - ) { - this.$scope = $scope; - this.genericReq = GenericRequest; - this.$location = $location; - this.$route = $route; - this.errorHandler = errorHandler; - this.csvReq = csvReq; - this.shareAgent = new ShareAgent(); - this.commonData = commonData; - this.wazuhConfig = new WazuhConfig(); - this.errorInit = false; - this.$window = $window; - this.addingNewAgent = false; - } - - /** - * On controller loads - */ - async $onInit() { - this.init = true; - this.api = JSON.parse(AppState.getCurrentAPI()).id; - const loc = this.$location.search(); - if ((loc || {}).agent && (loc || {}).agent !== '000') { - this.commonData.setTimefilter( - getDataPlugin().timefilter.timefilter.getTime(), - ); - return this.showAgent({ id: loc.agent }); - } - - this.isClusterEnabled = - AppState.getClusterInfo() && - AppState.getClusterInfo().status === 'enabled'; - this.loading = true; - this.osPlatforms = []; - this.versions = []; - this.groups = []; - this.nodes = []; - this.mostActiveAgent = { - name: '', - id: '', - }; - this.prevSearch = false; - - // Load URL params - if (loc && loc.tab) { - this.submenuNavItem = loc.tab; - } - // Watcher for URL params - this.$scope.$watch('submenuNavItem', () => { - this.$location.search('tab', this.submenuNavItem); - }); - - this.$scope.$on('wazuhFetched', evt => { - evt.stopPropagation(); - }); - this.registerAgentsProps = { - addNewAgent: flag => this.addNewAgent(flag), - hasAgents: () => this.hasAgents, - reload: () => this.$route.reload(), - getWazuhVersion: () => this.getWazuhVersion(), - getCurrentApiAddress: () => this.getCurrentApiAddress(), - }; - this.hasAgents = true; - this.init = false; - const instance = new DataFactory(WzRequest.apiReq, '/agents', false, false); - //Props - this.tableAgentsProps = { - updateSummary: summary => { - this.summary = summary; - if (this.summary.total === 0) { - this.addNewAgent(true); - this.hasAgents = false; - } else { - this.hasAgents = true; - } - }, - wzReq: (method, path, body) => WzRequest.apiReq(method, path, body), - addingNewAgent: () => { - this.addNewAgent(true); - this.$scope.$applyAsync(); - }, - downloadCsv: (filters = []) => { - this.downloadCsv(filters); - this.$scope.$applyAsync(); - }, - showAgent: agent => { - this.showAgent(agent); - this.$scope.$applyAsync(); - }, - getMostActive: async () => { - return await this.getMostActive(); - }, - clickAction: (item, openAction = false) => { - clickAction( - item, - openAction, - instance, - this.shareAgent, - this.$location, - this.$scope, - ); - this.$scope.$applyAsync(); - }, - formatUIDate: date => formatUIDate(date), - summary: this.summary, - }; - //Load - this.load(); - } - - /** - * Searches by a query and term - * @param {String} query - * @param {String} search - */ - query(query, search) { - this.$scope.$broadcast('wazuhQuery', { query, search }); - this.prevSearch = search || false; - } - - /** - * Selects an agent - * @param {String} agent - */ - showAgent(agent) { - this.shareAgent.setAgent(agent); - this.$location.path('/agents'); - } - - /** - * Exports the table in CSV format - */ - async downloadCsv(filters) { - try { - ErrorHandler.info('Your download should begin automatically...', 'CSV'); - const output = await this.csvReq.fetch('/agents', this.api, filters); - const blob = new Blob([output], { type: 'text/csv' }); // eslint-disable-line - - FileSaver.saveAs(blob, 'agents.csv'); - - return; - } catch (error) { - const options = { - context: `${AgentsPreviewController.name}.downloadCsv`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: `Error exporting CSV: ${error.message || error}`, - }, - }; - getErrorOrchestrator().handleError(options); - } - } - - async getMostActive() { - try { - const data = await this.genericReq.request( - 'GET', - `/elastic/top/${this.firstUrlParam}/${this.secondUrlParam}/agent.name/${ - this.pattern - }?agentsList=${store - .getState() - .appStateReducers.allowedAgents.toString()}`, - ); - this.mostActiveAgent.name = data.data.data; - const info = await this.genericReq.request( - 'GET', - `/elastic/top/${this.firstUrlParam}/${this.secondUrlParam}/agent.id/${ - this.pattern - }?agentsList=${store - .getState() - .appStateReducers.allowedAgents.toString()}`, - ); - if (info.data.data === '' && this.mostActiveAgent.name !== '') { - this.mostActiveAgent.id = '000'; - } else { - this.mostActiveAgent.id = info.data.data; - } - return this.mostActiveAgent; - } catch (error) { - const options = { - context: `${AgentsPreviewController.name}.getMostActive`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - store: true, - error: { - error: error, - message: error.message || error, - title: `An error occurred while trying to get the most active agent: ${ - error.message || error - }`, - }, - }; - getErrorOrchestrator().handleError(options); - } - } - - /** - * On controller loads - */ - async load() { - try { - this.errorInit = false; - const clusterInfo = AppState.getClusterInfo(); - this.firstUrlParam = - clusterInfo.status === 'enabled' ? 'cluster' : 'manager'; - this.secondUrlParam = clusterInfo[this.firstUrlParam]; - this.pattern = ( - await getDataPlugin().indexPatterns.get(AppState.getCurrentPattern()) - ).title; - } catch (error) { - const options = { - context: `${AgentsPreviewController.name}.load`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.CRITICAL, - store: true, - error: { - error: error, - message: error.message || error, - title: error.message || error, - }, - }; - getErrorOrchestrator().handleError(options); - } - this.loading = false; - this.$scope.$applyAsync(); - } - - addNewAgent(flag) { - this.addingNewAgent = flag; - } - - /** - * Returns the current API address - */ - async getCurrentApiAddress() { - try { - const result = await this.genericReq.request('GET', '/hosts/apis'); - const entries = result.data || []; - const host = entries.filter(e => { - return e.id == this.api; - }); - const url = host[0].url; - const numToClean = url.startsWith('https://') ? 8 : 7; - return url.substr(numToClean); - } catch (error) { - const options = { - context: `${AgentsPreviewController.name}.getCurrentApiAddress`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.UI, - error: { - error: error, - message: error.message || error, - title: `Could not get the Wazuh API address: ${ - error.message || error - }`, - }, - }; - getErrorOrchestrator().handleError(options); - } - } - - /** - * Returns the Wazuh version as x.y.z - */ - async getWazuhVersion() { - try { - const data = await WzRequest.apiReq('GET', '/', {}); - const result = ((data || {}).data || {}).data || {}; - return result.api_version; - } catch (error) { - const options = { - context: `${AgentsPreviewController.name}.getWazuhVersion`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: `Could not get the Wazuh version: ${error.message || error}`, - }, - }; - getErrorOrchestrator().handleError(options); - return version; - } - } -} diff --git a/plugins/main/public/controllers/agent/components/agents-preview.js b/plugins/main/public/controllers/agent/components/agents-preview.js deleted file mode 100644 index bb02a41ce3..0000000000 --- a/plugins/main/public/controllers/agent/components/agents-preview.js +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Wazuh app - React component for building the agents preview section. - * - * 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 PropTypes from 'prop-types'; -import { - EuiPage, - EuiFlexGroup, - EuiFlexItem, - EuiStat, - EuiLoadingChart, - EuiSpacer, - EuiToolTip, - EuiCard, - EuiLink, -} from '@elastic/eui'; -import { AgentsTable } from './agents-table'; -import { WzRequest } from '../../../react-services/wz-request'; -import KibanaVis from '../../../kibana-integrations/kibana-vis'; -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 { - withReduxProvider, - withGlobalBreadcrumb, - withUserAuthorizationPrompt, -} from '../../../components/common/hocs'; -import { formatUIDate } from '../../../../public/react-services/time-service'; -import { compose } from 'redux'; -import { withErrorBoundary } from '../../../components/common/hocs'; -import './agents-preview.scss'; -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 '../../../components/common/charts/visualizations/basic'; -import { - agentStatusColorByAgentStatus, - agentStatusLabelByAgentStatus, -} from '../../../../common/services/wz_agent_status'; -import { endpointSumary } from '../../../utils/applications'; - -export const AgentsPreview = compose( - withErrorBoundary, - withReduxProvider, - withGlobalBreadcrumb([{ text: endpointSumary.title }]), - withUserAuthorizationPrompt([ - [ - { action: 'agent:read', resource: 'agent:id:*' }, - { action: 'agent:read', resource: 'agent:group:*' }, - ], - ]), -)( - class AgentsPreview extends Component { - _isMount = false; - constructor(props) { - super(props); - this.state = { - loadingAgents: false, - loadingSummary: false, - showAgentsEvolutionVisualization: true, - agentTableFilters: [], - agentStatusSummary: { - active: '-', - disconnected: '-', - total: '-', - pending: '-', - never_connected: '-', - }, - agentConfiguration: {}, - agentsActiveCoverage: 0, - }; - this.wazuhConfig = new WazuhConfig(); - this.agentStatus = UI_ORDER_AGENT_STATUS.map(agentStatus => ({ - status: agentStatus, - label: agentStatusLabelByAgentStatus(agentStatus), - color: agentStatusColorByAgentStatus(agentStatus), - })); - } - - async componentDidMount() { - this._isMount = true; - this.fetchAgentStatusDetailsData(); - if (this.wazuhConfig.getConfig()['wazuh.monitoring.enabled']) { - this._isMount && - this.setState({ - showAgentsEvolutionVisualization: true, - }); - const tabVisualizations = new TabVisualizations(); - tabVisualizations.removeAll(); - tabVisualizations.setTab('general'); - tabVisualizations.assign({ - general: 1, - }); - const filterHandler = new FilterHandler(AppState.getCurrentPattern()); - await VisFactoryHandler.buildOverviewVisualizations( - filterHandler, - 'general', - null, - ); - } - } - - componentWillUnmount() { - this._isMount = false; - } - - groupBy = function (arr) { - return arr.reduce(function (prev, item) { - if (item in prev) prev[item]++; - else prev[item] = 1; - return prev; - }, {}); - }; - async fetchSummaryStatus() { - this.setState({ loadingSummary: true }); - const { - data: { - data: { - connection: agentStatusSummary, - configuration: agentConfiguration, - }, - }, - } = await WzRequest.apiReq('GET', '/agents/summary/status', {}); - - this.props.tableProps.updateSummary(agentStatusSummary); - - const agentsActiveCoverage = ( - (agentStatusSummary.active / agentStatusSummary.total) * - 100 - ).toFixed(2); - - this.setState({ - loadingSummary: false, - agentStatusSummary, - agentConfiguration, - /* Calculate the agents active coverage. - Ensure the calculated value is not a NaN, otherwise set a 0. - */ - agentsActiveCoverage: isNaN(agentsActiveCoverage) - ? 0 - : agentsActiveCoverage, - }); - } - - async fetchAgents() { - this.setState({ loadingAgents: true }); - const { - data: { - data: { - affected_items: [lastRegisteredAgent], - }, - }, - } = await WzRequest.apiReq('GET', '/agents', { - params: { limit: 1, sort: '-dateAdd', q: 'id!=000' }, - }); - const agentMostActive = await this.props.tableProps.getMostActive(); - this.setState({ - loadingAgents: false, - lastRegisteredAgent, - agentMostActive, - }); - } - async fetchAgentStatusDetailsData() { - try { - this.fetchSummaryStatus(); - this.fetchAgents(); - } catch (error) { - this.setState({ loadingAgents: false, loadingSummary: false }); - const options = { - context: `${AgentsPreview.name}.fetchAgentStatusDetailsData`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - store: true, - error: { - error: error, - message: error.message || error, - title: `Could not get the agents summary`, - }, - }; - getErrorOrchestrator().handleError(options); - } - } - - removeFilters() { - this._isMount && this.setState({ agentTableFilters: {} }); - } - - showAgent(agent) { - agent && this.props.tableProps.showAgent(agent); - } - - filterAgentByStatus(status) { - this._isMount && - this.setState({ - agentTableFilters: { q: `id!=000;status=${status}` }, - }); - } - onRenderComplete() { - this.setState({ - evolutionRenderComplete: true, - }); - } - - render() { - const evolutionIsReady = this.props.resultState !== 'loading'; - - 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.showAgent( - this.state.lastRegisteredAgent, - ) - } - > - {this.state.lastRegisteredAgent?.name || '-'} - - - } - titleSize='s' - description='Last registered agent' - titleColor='primary' - /> - - { - - - - this.showAgent(this.state.agentMostActive) - } - > - {this.state.agentMostActive?.name || '-'} - - - } - titleSize='s' - description='Most active agent' - titleColor='primary' - /> - - } - - - - - } - - - - -
- - - -
- {!evolutionIsReady && ( -
- -
- )} -
-
-
-
-
- - - this.removeFilters()} - wzReq={this.props.tableProps.wzReq} - addingNewAgent={this.props.tableProps.addingNewAgent} - downloadCsv={this.props.tableProps.downloadCsv} - formatUIDate={date => formatUIDate(date)} - reload={() => this.fetchAgentStatusDetailsData()} - /> - -
-
- ); - } - }, -); - -AgentsTable.propTypes = { - tableProps: PropTypes.object, - showAgent: PropTypes.func, -}; diff --git a/plugins/main/public/controllers/agent/components/checkUpgrade.tsx b/plugins/main/public/controllers/agent/components/checkUpgrade.tsx deleted file mode 100644 index a75d7c5f5e..0000000000 --- a/plugins/main/public/controllers/agent/components/checkUpgrade.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Wazuh app - React component for show search and filter - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -import React, { Component } from 'react'; -import { EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import { WzRequest } from '../../../react-services/wz-request'; -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 class CheckUpgrade extends Component { - props!: { - id: String; - version: String; - agent: Object; - upgrading: Boolean; - managerVersion: String; - changeStatusUpdate: Function; - reloadAgent: Function; - }; - interval: any; - - constructor(props) { - super(props); - } - - componentWillUnmount() { - clearInterval(this.interval); - } - - componentDidUpdate(prevProps) { - if (prevProps.upgrading !== this.props.upgrading) { - if (this.props.upgrading === true) - this.interval = setInterval(() => this.checkUpgrade(this.props.id), 3000); - } - } - - async checkUpgrade(agentId) { - try { - const response = await WzRequest.apiReq('GET', `/agents/${agentId}/upgrade_result`, {}); - if (response.data === 200) { - this.props.changeStatusUpdate(agentId); - this.props.reloadAgent(); - clearInterval(this.interval); - console.log(`${this.props.id} agent ends interval`); - } - } catch (error) { - const options = { - context: `${CheckUpgrade.name}.checkUpgrade`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: error.message || error, - }, - }; - getErrorOrchestrator().handleError(options); - } - } - - addUpgraingProgress() { - const { id, version, upgrading, managerVersion } = this.props; - - if (version === '.' || version === managerVersion) { - return; - } else if (upgrading === true) { - /* this.interval = setInterval(() => this.checkUpgrade(id), 30000); */ - return ( - - - - ); - } - } - - render() { - const { version } = this.props; - let upgrading = this.addUpgraingProgress(); - - return ( -
- {version} -   - {upgrading} -
- ); - } -} diff --git a/plugins/main/public/controllers/agent/components/register-agent-service.ts b/plugins/main/public/controllers/agent/components/register-agent-service.ts deleted file mode 100644 index bf453797b7..0000000000 --- a/plugins/main/public/controllers/agent/components/register-agent-service.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { WzRequest } from '../../../react-services/wz-request'; -import { ServerAddressOptions } from '../register-agent/steps'; - -type Protocol = 'TCP' | 'UDP'; - -type RemoteItem = { - connection: 'syslog' | 'secure'; - ipv6: 'yes' | 'no'; - protocol: Protocol[]; - allowed_ips?: string[]; - queue_size?: string; -}; - -type RemoteConfig = { - name: string; - isUdp: boolean | null; - haveSecureConnection: boolean | null; -}; - -/** - * Get the cluster status - */ -export const clusterStatusResponse = async (): Promise => { - const clusterStatus = await WzRequest.apiReq('GET', '/cluster/status', {}); - if ( - clusterStatus.data.data.enabled === 'yes' && - clusterStatus.data.data.running === 'yes' - ) { - // Cluster mode - return true; - } else { - // Manager mode - return false; - } -}; - -/** - * Get the remote configuration from api - */ -async function getRemoteConfiguration(nodeName: string): Promise { - let config: RemoteConfig = { - name: nodeName, - isUdp: false, - haveSecureConnection: false, - }; - - try { - const clusterStatus = await clusterStatusResponse(); - let result; - if (clusterStatus) { - result = await WzRequest.apiReq( - 'GET', - `/cluster/${nodeName}/configuration/request/remote`, - {}, - ); - } else { - result = await WzRequest.apiReq( - 'GET', - '/manager/configuration/request/remote', - {}, - ); - } - const items = ((result.data || {}).data || {}).affected_items || []; - const remote = items[0]?.remote; - if (remote) { - const remoteFiltered = remote.filter((item: RemoteItem) => { - return item.connection === 'secure'; - }); - - remoteFiltered.length > 0 - ? (config.haveSecureConnection = true) - : (config.haveSecureConnection = false); - - let protocolsAvailable: Protocol[] = []; - remote.forEach((item: RemoteItem) => { - // get all protocols available - item.protocol.forEach(protocol => { - protocolsAvailable = protocolsAvailable.concat(protocol); - }); - }); - - config.isUdp = - getRemoteProtocol(protocolsAvailable) === 'UDP' ? true : false; - } - return config; - }catch(error){ - return config; - } -} - -/** - * Get the remote protocol available from list of protocols - * @param protocols - */ -function getRemoteProtocol(protocols: Protocol[]) { - if (protocols.length === 1) { - return protocols[0]; - } else { - return !protocols.includes('TCP') ? 'UDP' : 'TCP'; - } -} - -/** - * Get the remote configuration from nodes registered in the cluster and decide the protocol to setting up in deploy agent param - * @param nodeSelected - * @param defaultServerAddress - */ -async function getConnectionConfig( - nodeSelected: ServerAddressOptions, - defaultServerAddress?: string, -) { - const nodeName = nodeSelected?.label; - const nodeIp = nodeSelected?.value; - if(!defaultServerAddress){ - if(nodeSelected.nodetype !== 'custom'){ - const remoteConfig = await getRemoteConfiguration(nodeName); - return { serverAddress: nodeIp, udpProtocol: remoteConfig.isUdp, connectionSecure: remoteConfig.haveSecureConnection }; - }else{ - return { serverAddress: nodeName, udpProtocol: false, connectionSecure: true }; - } - } else { - return { - serverAddress: defaultServerAddress, - udpProtocol: false, - connectionSecure: true, - }; - } -} - -type NodeItem = { - name: string; - ip: string; - type: string; -}; - -type NodeResponse = { - data: { - data: { - affected_items: NodeItem[]; - }; - }; -}; - -/** - * Get the list of the cluster nodes and parse it into a list of options - */ -export const getNodeIPs = async (): Promise => { - return await WzRequest.apiReq('GET', '/cluster/nodes', {}); -}; - -/** - * Get the list of the manager and parse it into a list of options - */ -export const getManagerNode = async (): Promise => { - const managerNode = await WzRequest.apiReq('GET', '/manager/api/config', {}); - return managerNode?.data?.data?.affected_items?.map(item => ({ - label: item.node_name, - value: item.node_api_config.host, - nodetype: 'master', - })) || []; -}; - -/** - * Parse the nodes list from the API response to a format that can be used by the EuiComboBox - * @param nodes - */ -export const parseNodesInOptions = ( - nodes: NodeResponse, -): ServerAddressOptions[] => { - return nodes.data.data.affected_items.map((item: NodeItem) => ({ - label: item.name, - value: item.ip, - nodetype: item.type, - })); -}; - -/** - * Get the list of the cluster nodes from API and parse it into a list of options - */ -export const fetchClusterNodesOptions = async (): Promise< - ServerAddressOptions[] -> => { - const clusterStatus = await clusterStatusResponse(); - if (clusterStatus) { - // Cluster mode - // Get the cluster nodes - const nodes = await getNodeIPs(); - return parseNodesInOptions(nodes); - } else { - // Manager mode - // Get the manager node - return await getManagerNode(); - } -}; - -/** - * Get the master node data from the list of cluster nodes - * @param nodeIps - */ -export const getMasterNode = ( - nodeIps: ServerAddressOptions[], -): ServerAddressOptions[] => { - return nodeIps.filter(nodeIp => nodeIp.nodetype === 'master'); -}; - -/** - * Get the remote configuration from manager - * This function get the config from manager mode or cluster mode - */ -export const getMasterRemoteConfiguration = async () => { - const nodes = await fetchClusterNodesOptions(); - const masterNode = getMasterNode(nodes); - return await getRemoteConfiguration(masterNode[0].label); -} - - - -export { getConnectionConfig, getRemoteConfiguration }; diff --git a/plugins/main/public/controllers/agent/components/register-agent.js b/plugins/main/public/controllers/agent/components/register-agent.js deleted file mode 100644 index 37395e6ea6..0000000000 --- a/plugins/main/public/controllers/agent/components/register-agent.js +++ /dev/null @@ -1,2291 +0,0 @@ -/* - * Wazuh app - React component for registering agents. - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ -import React, { Component, Fragment } from 'react'; -import { version } from '../../../../package.json'; -import { WazuhConfig } from '../../../react-services/wazuh-config'; -import { - EuiSteps, - EuiTabbedContent, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiComboBox, - EuiFieldText, - EuiText, - EuiCodeBlock, - EuiTitle, - EuiButtonEmpty, - EuiCopy, - EuiPage, - EuiPageBody, - EuiCallOut, - EuiSpacer, - EuiProgress, - EuiIcon, - EuiSwitch, - EuiLink, - EuiFormRow, - EuiForm, -} from '@elastic/eui'; -import { WzRequest } from '../../../react-services/wz-request'; -import { withErrorBoundary } from '../../../components/common/hocs'; -import { UI_LOGGER_LEVELS } from '../../../../common/constants'; -import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; -import { getErrorOrchestrator } from '../../../react-services/common-services'; -import { webDocumentationLink } from '../../../../common/services/web_documentation'; -import { - architectureButtons, - architectureButtonsi386, - architecturei386Andx86_64, - versionButtonsRaspbian, - versionButtonsSuse, - versionButtonsOracleLinux, - versionButtonFedora, - architectureButtonsSolaris, - architectureButtonsWithPPC64LE, - architectureButtonsAix, - architectureButtonsHpUx, - versionButtonAmazonLinux, - versionButtonsRedHat, - versionButtonsCentos, - architectureButtonsMacos, - osPrincipalButtons, - versionButtonsDebian, - versionButtonsUbuntu, - versionButtonsWindows, - versionButtonsMacOS, - versionButtonsOpenSuse, - versionButtonsSolaris, - versionButtonsAix, - versionButtonsHPUX, - versionButtonAlpine, - architectureButtonsWithPPC64LEAlpine, -} from '../wazuh-config'; -import './register-agent.scss'; -import WzManagerAddressInput from '../register-agent/steps/wz-manager-address'; -import { getMasterRemoteConfiguration } from './register-agent-service'; -import { PrincipalButtonGroup } from './wz-accordion'; -import RegisterAgentButtonGroup from '../register-agent/register-agent-button-group'; -import '../../../styles/common.scss'; - -export const RegisterAgent = withErrorBoundary( - class RegisterAgent extends Component { - constructor(props) { - super(props); - this.wazuhConfig = new WazuhConfig(); - this.configuration = this.wazuhConfig.getConfig(); - this.addToVersion = '-1'; - - this.state = { - status: 'incomplete', - selectedOS: '', - selectedSYS: '', - neededSYS: false, - selectedArchitecture: '', - selectedVersion: '', - version: '', - wazuhVersion: '', - serverAddress: '', - agentName: '', - agentNameError: false, - badCharacters: [], - wazuhPassword: '', - groups: [], - selectedGroup: [], - defaultServerAddress: '', - udpProtocol: false, - showPassword: false, - showProtocol: true, - connectionSecure: true, - isAccordionOpen: false, - }; - this.restartAgentCommand = { - rpm: this.systemSelector(), - cent: this.systemSelector(), - deb: this.systemSelector(), - ubu: this.systemSelector(), - oraclelinux: this.systemSelector(), - macos: this.systemSelectorWazuhControlMacos(), - win: this.systemSelectorNet(), - }; - } - - async componentDidMount() { - try { - this.setState({ loading: true }); - const wazuhVersion = await this.props.getWazuhVersion(); - let wazuhPassword = ''; - let hidePasswordInput = false; - this.getEnrollDNSConfig(); - await this.getRemoteConfig(); - let authInfo = await this.getAuthInfo(); - const needsPassword = (authInfo.auth || {}).use_password === 'yes'; - if (needsPassword) { - wazuhPassword = - this.configuration['enrollment.password'] || - authInfo['authd.pass'] || - ''; - if (wazuhPassword) { - hidePasswordInput = true; - } - } - const groups = await this.getGroups(); - this.setState({ - needsPassword, - hidePasswordInput, - versionButtonsRedHat, - versionButtonsCentos, - versionButtonsDebian, - versionButtonsUbuntu, - versionButtonsWindows, - versionButtonsMacOS, - versionButtonsOpenSuse, - versionButtonsSolaris, - versionButtonAmazonLinux, - versionButtonsSuse, - versionButtonsAix, - versionButtonsHPUX, - versionButtonsOracleLinux, - versionButtonsRaspbian, - versionButtonFedora, - architectureButtons, - architectureButtonsi386, - architecturei386Andx86_64, - architectureButtonsSolaris, - architectureButtonsAix, - architectureButtonsHpUx, - architectureButtonsMacos, - architectureButtonsWithPPC64LE, - wazuhPassword, - wazuhVersion, - groups, - loading: false, - }); - } catch (error) { - this.setState({ - wazuhVersion: version, - loading: false, - }); - const options = { - context: `${RegisterAgent.name}.componentDidMount`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - display: true, - store: false, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - getErrorOrchestrator().handleError(options); - } - } - - getEnrollDNSConfig = () => { - const serverAddress = this.configuration['enrollment.dns'] || ''; - this.setState({ defaultServerAddress: serverAddress }); - }; - - getRemoteConfig = async () => { - const remoteConfig = await getMasterRemoteConfiguration(); - if (remoteConfig) { - this.setState({ - haveUdpProtocol: remoteConfig.isUdp, - haveConnectionSecure: remoteConfig.haveSecureConnection, - udpProtocol: remoteConfig.isUdp, - connectionSecure: remoteConfig.haveSecureConnection, - }); - } - }; - - async getAuthInfo() { - try { - const result = await WzRequest.apiReq( - 'GET', - '/agents/000/config/auth/auth', - {}, - ); - return (result.data || {}).data || {}; - } catch (error) { - this.setState({ gotErrorRegistrationServiceInfo: true }); - throw new Error(error); - } - } - - selectOS(os) { - this.setState({ - selectedOS: os, - selectedVersion: '', - selectedArchitecture: '', - selectedSYS: '', - }); - } - - systemSelector() { - if ( - this.state.selectedVersion === 'redhat7' || - this.state.selectedVersion === 'amazonlinux2022' || - this.state.selectedVersion === 'centos7' || - this.state.selectedVersion === 'suse11' || - this.state.selectedVersion === 'suse12' || - this.state.selectedVersion === 'oraclelinux5' || - this.state.selectedVersion === 'oraclelinux6' || - this.state.selectedVersion === '22' || - this.state.selectedVersion === 'amazonlinux2' || - this.state.selectedVersion === 'debian8' || - this.state.selectedVersion === 'debian9' || - this.state.selectedVersion === 'debian10' || - this.state.selectedVersion === 'busterorgreater' || - this.state.selectedVersion === 'ubuntu15' || - this.state.selectedVersion === 'leap15' - ) { - return 'sudo systemctl daemon-reload\nsudo systemctl enable wazuh-agent\nsudo systemctl start wazuh-agent'; - } else if ( - this.state.selectedVersion === 'redhat5' || - this.state.selectedVersion === 'redhat6' || - this.state.selectedVersion === 'centos5' || - this.state.selectedVersion === 'centos6' || - this.state.selectedVersion === 'amazonlinux1' || - this.state.selectedVersion === 'debian7' || - this.state.selectedVersion === 'ubuntu14' - ) { - return 'service wazuh-agent start'; - } else { - return ''; - } - } - - systemSelectorNet() { - if ( - this.state.selectedVersion === 'windowsxp' || - this.state.selectedVersion === 'windowsserver2008' || - this.state.selectedVersion === 'windows7' - ) { - return 'NET START Wazuh'; - } else { - return ''; - } - } - - systemSelectorWazuhControlMacos() { - if (this.state.selectedVersion == 'sierra') { - return 'sudo /Library/Ossec/bin/wazuh-control start'; - } else { - return ''; - } - } - - systemSelectorWazuhControl() { - if ( - this.state.selectedVersion === 'solaris10' || - this.state.selectedVersion === 'solaris11' || - this.state.selectedVersion === '6.1 TL9' || - this.state.selectedVersion === '3.12.12' - ) { - return '/var/ossec/bin/wazuh-control start'; - } else { - return ''; - } - } - - systemSelectorInitD() { - if (this.state.selectedVersion === '11.31') { - return '/sbin/init.d/wazuh-agent start'; - } else { - return ''; - } - } - - selectSYS(sys) { - this.setState({ selectedSYS: sys }); - } - - setServerAddress(serverAddress) { - this.setState({ serverAddress }); - } - - setAgentName(event) { - const validation = /^[a-z0-9-_.]+$/i; - if ( - (validation.test(event.target.value) && - event.target.value.length >= 2) || - event.target.value.length <= 0 - ) { - this.setState({ - agentName: event.target.value, - agentNameError: false, - badCharacters: [], - }); - } else { - let badCharacters = event.target.value - .split('') - .map(char => char.replace(validation, '')) - .join(''); - badCharacters = badCharacters - .split('') - .map(char => char.replace(/\s/, 'whitespace')); - const characters = [...new Set(badCharacters)]; - this.setState({ - agentName: event.target.value, - badCharacters: characters, - agentNameError: true, - }); - } - } - - setGroupName(selectedGroup) { - this.setState({ selectedGroup }); - } - - setArchitecture(selectedArchitecture) { - this.setState({ selectedArchitecture }); - } - - setVersion(selectedVersion) { - this.setState({ selectedVersion, selectedArchitecture: '' }); - } - - setWazuhPassword(event) { - this.setState({ wazuhPassword: event.target.value }); - } - - setShowPassword(event) { - this.setState({ showPassword: event.target.checked }); - } - - obfuscatePassword(text) { - let obfuscate = ''; - const regex = /WAZUH_REGISTRATION_PASSWORD=?\040?\'(.*?)\'[\"| ]/gm; - const match = regex.exec(text); - const password = match[1]; - if (password) { - [...password].forEach(() => (obfuscate += '*')); - text = text.replace(password, obfuscate); - } - return text; - } - - async getGroups() { - try { - const result = await WzRequest.apiReq('GET', '/groups', {}); - return result.data.data.affected_items.map(item => ({ - label: item.name, - id: item.name, - })); - } catch (error) { - throw new Error(error); - } - } - - optionalDeploymentVariables() { - const escapeQuotes = value => value.replace(/'/g, "\\'"); - let deployment = - this.state.serverAddress && - `WAZUH_MANAGER='${escapeQuotes(this.state.serverAddress)}' `; - if (this.state.selectedOS == 'win') { - deployment += `WAZUH_REGISTRATION_SERVER='${escapeQuotes( - this.state.serverAddress, - )}' `; - } - - if (this.state.needsPassword) { - deployment += `WAZUH_REGISTRATION_PASSWORD='${escapeQuotes( - this.state.wazuhPassword, - )}' `; - } - - if (this.state.udpProtocol) { - deployment += "WAZUH_PROTOCOL='UDP' "; - } - - if (this.state.selectedGroup.length) { - deployment += `WAZUH_AGENT_GROUP='${this.state.selectedGroup - .map(item => item.label) - .join(',')}' `; - } - - return deployment; - } - - agentNameVariable() { - let agentName = `WAZUH_AGENT_NAME='${this.state.agentName}' `; - if (this.state.selectedArchitecture && this.state.agentName !== '') { - return agentName; - } else { - return ''; - } - } - - resolveRPMPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'redhat5-i386': - return `https://packages.wazuh.com/4.x/yum5/i386/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.i386.rpm`; - case 'redhat5-x86_64': - return `https://packages.wazuh.com/4.x/yum5/x86_64/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.x86_64.rpm`; - case 'redhat6-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'redhat6-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'redhat6-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'redhat6-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'redhat7-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'redhat7-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'redhat7-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'redhat7-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'redhat7-powerpc': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.ppc64le.rpm`; - default: - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - resolveAlpinePackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case '3.12.12-i386': - return 'https://packages.wazuh.com/key/alpine-devel%40wazuh.com-633d7457.rsa.pub && echo "https://packages.wazuh.com/4.x/alpine/v3.12/main"'; - case '3.12.12-aarch64': - return 'https://packages.wazuh.com/key/alpine-devel%40wazuh.com-633d7457.rsa.pub && echo "https://packages.wazuh.com/4.x/alpine/v3.12/main"'; - case '3.12.12-x86_64': - return 'https://packages.wazuh.com/key/alpine-devel%40wazuh.com-633d7457.rsa.pub && echo "https://packages.wazuh.com/4.x/alpine/v3.12/main"'; - case '3.12.12-x86': - return 'https://packages.wazuh.com/key/alpine-devel%40wazuh.com-633d7457.rsa.pub && echo "https://packages.wazuh.com/4.x/alpine/v3.12/main"'; - case '3.12.12-armhf': - return 'https://packages.wazuh.com/key/alpine-devel%40wazuh.com-633d7457.rsa.pub && echo "https://packages.wazuh.com/4.x/alpine/v3.12/main"'; - case '3.12.12-powerpc': - return 'https://packages.wazuh.com/key/alpine-devel%40wazuh.com-633d7457.rsa.pub && echo "https://packages.wazuh.com/4.x/alpine/v3.12/main"'; - default: - return 'https://packages.wazuh.com/key/alpine-devel%40wazuh.com-633d7457.rsa.pub && echo "https://packages.wazuh.com/4.x/alpine/v3.12/main"'; - } - } - - resolveORACLELINUXPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'oraclelinux5-i386': - return `https://packages.wazuh.com/4.x/yum5/i386/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.i386.rpm`; - case 'oraclelinux5-x86_64': - return `https://packages.wazuh.com/4.x/yum5/x86_64/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.x86_64.rpm`; - case 'oraclelinux6-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'oraclelinux6-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'oraclelinux6-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'oraclelinux6-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - default: - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - resolveCENTPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'centos5-i386': - return `https://packages.wazuh.com/4.x/yum5/i386/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.i386.rpm`; - case 'centos5-x86_64': - return `https://packages.wazuh.com/4.x/yum5/x86_64/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.x86_64.rpm`; - case 'centos6-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'centos6-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'centos6-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'centos6-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'centos7-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'centos7-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'centos7-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'centos7-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'centos7-powerpc': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.ppc64le.rpm`; - default: - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - resolveSUSEPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'suse11-i386': - return `https://packages.wazuh.com/4.x/yum5/i386/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.i386.rpm`; - case 'suse11-x86_64': - return `https://packages.wazuh.com/4.x/yum5/x86_64/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.el5.x86_64.rpm`; - case 'suse12-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'suse12-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'suse12-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'suse12-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'suse12-powerpc': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.ppc64le.rpm`; - default: - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - resolveFEDORAPachage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case '22-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case '22-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case '22-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case '22-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case '22-powerpc': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.ppc64le.rpm`; - default: - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - resolveAMAZONLPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'amazonlinux1-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'amazonlinux1-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'amazonlinux1-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'amazonlinux1-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'amazonlinux2-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'amazonlinux2-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'amazonlinux2-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'amazonlinux2-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'amazonlinux2022-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'amazonlinux2022-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'amazonlinux2022-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'amazonlinux2022-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - default: - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - resolveDEBPackage() { - switch (`${this.state.selectedArchitecture}`) { - case 'i386': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_i386.deb`; - case 'aarch64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_arm64.deb`; - case 'armhf': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_armhf.deb`; - case 'x86_64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_amd64.deb`; - case 'powerpc': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_ppc64el.deb`; - default: - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_amd64.deb`; - } - } - - resolveRASPBIANPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'busterorgreater-i386': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_i386.deb`; - case 'busterorgreater-aarch64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_arm64.deb`; - case 'busterorgreater-armhf': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_armhf.deb`; - case 'busterorgreater-x86_64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_amd64.deb`; - case 'busterorgreater-powerpc': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_ppc64el.deb`; - default: - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_amd64.deb`; - } - } - - resolveUBUNTUPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'ubuntu14-i386': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_i386.deb`; - case 'ubuntu14-aarch64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_arm64.deb`; - case 'ubuntu14-armhf': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_armhf.deb`; - case 'ubuntu14-x86_64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_amd64.deb`; - case 'ubuntu15-i386': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_i386.deb`; - case 'ubuntu15-aarch64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_arm64.deb`; - case 'ubuntu15-armhf': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_armhf.deb`; - case 'ubuntu15-x86_64': - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_amd64.deb`; - default: - return `https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_${this.state.wazuhVersion}${this.addToVersion}_amd64.deb`; - } - } - - resolveOPENSUSEPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'leap15-i386': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.i386.rpm`; - case 'leap15-aarch64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aarch64.rpm`; - case 'leap15-x86_64': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - case 'leap15-armhf': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.armv7hl.rpm`; - case 'leap15-powerpc': - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.ppc64le.rpm`; - default: - return `https://packages.wazuh.com/4.x/yum/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - resolveSOLARISPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case 'solaris10-i386': - return `https://packages.wazuh.com/4.x/solaris/i386/10/wazuh-agent_v${this.state.wazuhVersion}-sol10-i386.pkg`; - case 'solaris10-sparc': - return `https://packages.wazuh.com/4.x/solaris/sparc/10/wazuh-agent_v${this.state.wazuhVersion}-sol10-sparc.pkg`; - case 'solaris11-i386': - return `https://packages.wazuh.com/4.x/solaris/i386/11/wazuh-agent_v${this.state.wazuhVersion}-sol11-i386.p5p`; - case 'solaris11-sparc': - return `https://packages.wazuh.com/4.x/solaris/sparc/11/wazuh-agent_v${this.state.wazuhVersion}-sol11-sparc.p5p`; - default: - return `https://packages.wazuh.com/4.x/solaris/sparc/11/wazuh-agent_v${this.state.wazuhVersion}-sol11-sparc.p5p`; - } - } - - resolveAIXPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case '6.1 TL9-powerpc': - return `https://packages.wazuh.com/4.x/aix/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aix.ppc.rpm`; - default: - return `https://packages.wazuh.com/4.x/aix/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.aix.ppc.rpm`; - } - } - - resolveHPPackage() { - switch ( - `${this.state.selectedVersion}-${this.state.selectedArchitecture}` - ) { - case '11.31-itanium2': - return `https://packages.wazuh.com/4.x/hp-ux/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}-hpux-11v3-ia64.tar`; - default: - return `https://packages.wazuh.com/4.x/hp-ux/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}-hpux-11v3-ia64.tar`; - } - } - - optionalPackages() { - switch (this.state.selectedOS) { - case 'rpm': - return this.resolveRPMPackage(); - case 'cent': - return this.resolveCENTPackage(); - case 'deb': - return this.resolveDEBPackage(); - case 'ubu': - return this.resolveUBUNTUPackage(); - case 'open': - return this.resolveOPENSUSEPackage(); - case 'sol': - return this.resolveSOLARISPackage(); - case 'aix': - return this.resolveAIXPackage(); - case 'hp': - return this.resolveHPPackage(); - case 'amazonlinux': - return this.resolveAMAZONLPackage(); - case 'fedora': - return this.resolveFEDORAPachage(); - case 'oraclelinux': - return this.resolveORACLELINUXPackage(); - case 'suse': - return this.resolveSUSEPackage(); - case 'raspbian': - return this.resolveRASPBIANPackage(); - case 'alpine': - return this.resolveAlpinePackage(); - default: - return `https://packages.wazuh.com/4.x/yum/x86_64/wazuh-agent-${this.state.wazuhVersion}${this.addToVersion}.x86_64.rpm`; - } - } - - checkMissingOSSelection() { - if (!this.state.selectedOS) { - return ['Operating system']; - } - switch (this.state.selectedOS) { - case 'rpm': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'cent': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'deb': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'ubu': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'win': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'macos': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'open': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'sol': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'aix': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'hp': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'amazonlinux': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'fedora': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'oraclelinux': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'suse': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'raspbian': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - case 'alpine': - return [ - ...(!this.state.selectedVersion ? ['OS version'] : []), - ...(this.state.selectedVersion && !this.state.selectedArchitecture - ? ['OS architecture'] - : []), - ]; - default: - return []; - } - } - - getHighlightCodeLanguage(selectedSO) { - if (selectedSO.toLowerCase() === 'win') { - return 'powershell'; - } else { - return 'bash'; - } - } - - render() { - const appVersionMajorDotMinor = this.state.wazuhVersion - .split('.') - .slice(0, 2) - .join('.'); - const urlCheckConnectionDocumentation = webDocumentationLink( - 'user-manual/agents/agent-connection.html', - appVersionMajorDotMinor, - ); - - const urlWazuhAgentEnrollment = webDocumentationLink( - 'user-manual/agent-enrollment/index.html', - appVersionMajorDotMinor, - ); - - const urlWindowsPackage = `https://packages.wazuh.com/4.x/windows/wazuh-agent-${this.state.wazuhVersion}-1.msi`; - const missingOSSelection = this.checkMissingOSSelection(); - const warningForAgentName = - 'The agent name must be unique. It canā€™t be changed once the agent has been enrolled.'; - - const agentName = ( - -

- The deployment sets the endpoint hostname as the agent name by - default. Optionally, you can set the agent name below. -

- Assign an agent name - - - ` "${char}"`)} - ${this.state.badCharacters.length <= 1 ? 'is' : 'are'} - not valid. Allowed characters are A-Z, a-z, ".", "-", "_"`, - ]} - > - this.setAgentName(event)} - /> - - - - -
- ); - const groupInput = ( - <> - {!this.state.groups.length && ( - <> - - - )} - - ); - - const agentGroup = ( - -

Select one or more existing groups

- { - this.setGroupName(group); - }} - isDisabled={!this.state.groups.length} - isClearable={true} - data-test-subj='demoComboBox' - /> -
- ); - const passwordInput = ( - this.setWazuhPassword(event)} - /> - ); - - const codeBlock = { - zIndex: '100', - }; - - /*** macOS installation script customization ***/ - - // Set macOS installation script with environment variables - const macOSInstallationOptions = - `${this.optionalDeploymentVariables()}${this.agentNameVariable()}` - .replace(/\' ([a-zA-Z])/g, "' && $1") // Separate environment variables with && - .replace(/\"/g, '\\"') // Escape double quotes - .trim(); - - // If no variables are set, the echo will be empty - const macOSInstallationSetEnvVariablesScript = macOSInstallationOptions - ? `sudo echo "${macOSInstallationOptions}" > /tmp/wazuh_envs && ` - : ``; - - // Merge environment variables with installation script - const macOSInstallationScript = `curl -so wazuh-agent.pkg https://packages.wazuh.com/4.x/macos/wazuh-agent-${this.state.wazuhVersion}-1.${this.state.selectedArchitecture}.pkg && ${macOSInstallationSetEnvVariablesScript}sudo installer -pkg ./wazuh-agent.pkg -target /`; - - /*** end macOS installation script customization ***/ - - const customTexts = { - rpmText: `sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}yum install -y ${this.optionalPackages()}`, - alpineText: `wget -O /etc/apk/keys/alpine-devel@wazuh.com-633d7457.rsa.pub ${this.optionalPackages()} >> /etc/apk/repositories && \ -apk update && \ -apk add wazuh-agent=${this.state.wazuhVersion}-r1`, - centText: `sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}yum install -y ${this.optionalPackages()}`, - debText: `curl -so wazuh-agent.deb ${this.optionalPackages()} && sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}dpkg -i ./wazuh-agent.deb`, - ubuText: `curl -so wazuh-agent.deb ${this.optionalPackages()} && sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}dpkg -i ./wazuh-agent.deb`, - macosText: macOSInstallationScript, - winText: - this.state.selectedVersion == 'windowsxp' || - this.state.selectedVersion == 'windowsserver2008' - ? `msiexec.exe /i wazuh-agent-${ - this.state.wazuhVersion - }-1.msi /q ${this.optionalDeploymentVariables()}${this.agentNameVariable()}` - : `Invoke-WebRequest -Uri https://packages.wazuh.com/4.x/windows/wazuh-agent-${ - this.state.wazuhVersion - }-1.msi -OutFile \${env:tmp}\\wazuh-agent.msi; msiexec.exe /i \${env:tmp}\\wazuh-agent.msi /q ${this.optionalDeploymentVariables()}${this.agentNameVariable()}`, - openText: `sudo rpm --import https://packages.wazuh.com/key/GPG-KEY-WAZUH && sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}zypper install -y ${this.optionalPackages()}`, - solText: `sudo curl -so ${ - this.state.selectedVersion == 'solaris11' - ? 'wazuh-agent.p5p' - : 'wazuh-agent.pkg' - } ${this.optionalPackages()} && ${ - this.state.selectedVersion == 'solaris11' - ? 'pkg install -g wazuh-agent.p5p wazuh-agent' - : 'pkgadd -d wazuh-agent.pkg' - }`, - aixText: `sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}rpm -ivh ${this.optionalPackages()}`, - hpText: `cd / && sudo curl -so wazuh-agent.tar ${this.optionalPackages()} && sudo groupadd wazuh && sudo useradd -G wazuh wazuh && sudo tar -xvf wazuh-agent.tar`, - amazonlinuxText: `sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}yum install -y ${this.optionalPackages()}`, - fedoraText: `sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}yum install -y ${this.optionalPackages()}`, - oraclelinuxText: `sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}yum install -y ${this.optionalPackages()}`, - suseText: `sudo rpm --import https://packages.wazuh.com/key/GPG-KEY-WAZUH && sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}zypper install -y ${this.optionalPackages()}`, - raspbianText: `curl -so wazuh-agent.deb ${this.optionalPackages()} && sudo ${this.optionalDeploymentVariables()}${this.agentNameVariable()}dpkg -i ./wazuh-agent.deb`, - }; - - const field = `${this.state.selectedOS}Text`; - const text = customTexts[field]; - const language = this.getHighlightCodeLanguage(this.state.selectedOS); - const warningUpgrade = - 'If the installer finds another Wazuh agent in the system, it will upgrade it preserving the configuration.'; - const textAndLinkToCheckConnectionDocumentation = ( -

- To verify the connection with the Wazuh server, please follow this{' '} - - document. - -

- ); - - const warningCommand = ( - <> -

- Please - download - the package from our repository and copy it to the Windows system - where you are going to install it. Then run the following command to - perform the installation: -

- - ); - - const windowsAdvice = this.state.selectedOS === 'win' && ( - <> - -
    -
  • - - You will need administrator privileges to perform this - installation. - -
  • -
  • - PowerShell 3.0 or greater is required. -
  • -
-

- Keep in mind you need to run this command in a Windows PowerShell - terminal. -

-
- - - ); - const restartAgentCommand = - this.restartAgentCommand[this.state.selectedOS]; - const onTabClick = selectedTab => { - this.selectSYS(selectedTab.id); - }; - - const calloutErrorRegistrationServiceInfo = this.state - .gotErrorRegistrationServiceInfo ? ( - - ) : null; - - const guide = ( -
- {this.state.gotErrorRegistrationServiceInfo ? ( - - ) : ( - this.state.selectedOS && ( - - {this.state.agentName.length > 0 ? ( -

- You can use this command to install and enroll the Wazuh - agent. -

- ) : ( -

- You can use this command to install and enroll the Wazuh - agent in one or more hosts. -

- )} - - - {!this.state.connectionSecure && ( - <> - - {/** Warning connection NO SECURE */} - - Warning: there's no{' '} - - secure protocol configured - {' '} - and agents will not be able to communicate with the - manager. - - } - iconType='iInCircle' - /> - {/** END Warning connection NO SECURE */} - - )} - - {windowsAdvice} - {['windowsxp', 'windowsserver2008'].includes( - this.state.selectedVersion, - ) && ( - <> - - - - )} -
- - {this.state.wazuhPassword && - !this.state.showPassword && - !['sol', 'hp', 'alpine'].includes(this.state.selectedOS) - ? this.obfuscatePassword(text) - : text} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
- {this.state.selectedVersion == 'solaris10' || - this.state.selectedVersion == 'solaris11' ? ( - <> - - Might require some extra installation{' '} - - steps - - . - - } - > - - - After installing the agent, you need to enroll it in - the Wazuh server. Check the Wazuh agent enrollment{' '} - - Wazuh agent enrollment - {' '} - section to learn more. - - } - > - - ) : this.state.selectedVersion == '6.1 TL9' ? ( - <> - - Might require some extra installation{' '} - - steps - - . - - } - > - - - ) : this.state.selectedVersion == '11.31' ? ( - <> - - Might require some extra installation{' '} - - steps - - . - - } - > - - - After installing the agent, you need to enroll it in - the Wazuh server. Check the Wazuh agent enrollment{' '} - - Wazuh agent enrollment{' '} - - section to learn more. - - } - > - - ) : this.state.selectedVersion == '3.12.12' ? ( - <> - - Might require some extra installation{' '} - - steps - - . - - } - > - - - After installing the agent, you need to enroll it in - the Wazuh server. Check the Wazuh agent enrollment{' '} - - Wazuh agent enrollment{' '} - - section to learn more. - - } - > - - ) : this.state.selectedVersion == 'debian7' || - this.state.selectedVersion == 'debian8' || - this.state.selectedVersion == 'debian9' || - this.state.selectedVersion == 'debian10' ? ( - <> - - Might require some extra installation{' '} - - steps - - . - - } - > - - - ) : ( - '' - )} - {this.state.needsPassword && - !['sol', 'hp', 'alpine'].includes(this.state.selectedOS) ? ( - this.setShowPassword(active)} - /> - ) : ( - '' - )} - -
- ) - )} -
- ); - - const tabSysV = [ - { - id: 'sysV', - name: 'SysV Init', - content: ( - - - -
- - {this.systemSelector()} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
- - {textAndLinkToCheckConnectionDocumentation} -
-
- ), - }, - ]; - - const tabSystemD = [ - { - id: 'systemd', - name: 'Systemd', - content: ( - - - -
- - {this.systemSelector()} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
- - {textAndLinkToCheckConnectionDocumentation} -
-
- ), - }, - ]; - - const tabNet = [ - { - id: 'NET', - name: 'NET', - content: ( - - - -
- - {this.systemSelectorNet()} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
- - {textAndLinkToCheckConnectionDocumentation} -
-
- ), - }, - ]; - - const tabWazuhControlMacos = [ - { - id: 'Wazuh-control-macos', - name: 'Wazuh-control-macos', - content: ( - - - -
- - {this.systemSelectorWazuhControlMacos()} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
- - {textAndLinkToCheckConnectionDocumentation} -
-
- ), - }, - ]; - - const tabWazuhControl = [ - { - id: 'Wazuh-control', - name: 'Wazuh-control', - content: ( - - - -
- - {this.systemSelectorWazuhControl()} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
- - {textAndLinkToCheckConnectionDocumentation} -
-
- ), - }, - ]; - - const tabInitD = [ - { - id: 'Init.d', - name: 'Init.d', - content: ( - - - -
- - {this.systemSelectorInitD()} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
- - {textAndLinkToCheckConnectionDocumentation} -
-
- ), - }, - ]; - - const onChangeServerAddress = async nodeSelected => { - this.setState({ - serverAddress: nodeSelected, - udpProtocol: this.state.haveUdpProtocol, - connectionSecure: this.state.haveConnectionSecure, - }); - }; - - const steps = [ - { - title: 'Choose the operating system', - children: ( - this.selectOS(os)} - /> - ), - }, - ...(this.state.selectedOS == 'rpm' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'oraclelinux' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'raspbian' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'amazonlinux' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'cent' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'fedora' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'deb' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'ubu' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'win' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'macos' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'suse' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'open' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'sol' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'aix' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'hp' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedOS == 'alpine' - ? [ - { - title: 'Choose the version', - children: ( - this.setVersion(version)} - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == 'centos5' || - this.state.selectedVersion == 'redhat5' || - this.state.selectedVersion == 'oraclelinux5' || - this.state.selectedVersion == 'suse11' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == 'leap15' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == '3.12.12' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == 'centos6' || - this.state.selectedVersion == 'oraclelinux6' || - this.state.selectedVersion == 'amazonlinux1' || - this.state.selectedVersion == 'redhat6' || - this.state.selectedVersion == 'amazonlinux2022' || - this.state.selectedVersion == 'amazonlinux2' || - this.state.selectedVersion == 'debian7' || - this.state.selectedVersion == 'debian8' || - this.state.selectedVersion == 'ubuntu14' || - this.state.selectedVersion == 'ubuntu15' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == 'centos7' || - this.state.selectedVersion == 'redhat7' || - this.state.selectedVersion == 'suse12' || - this.state.selectedVersion == '22' || - this.state.selectedVersion == 'debian9' || - this.state.selectedVersion == 'busterorgreater' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == 'windowsxp' || - this.state.selectedVersion == 'windowsserver2008' || - this.state.selectedVersion == 'windows7' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == 'sierra' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == 'solaris10' || - this.state.selectedVersion == 'solaris11' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == '6.1 TL9' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(this.state.selectedVersion == '11.31' - ? [ - { - title: 'Choose the architecture', - children: ( - - this.setArchitecture(architecture) - } - /> - ), - }, - ] - : []), - ...(!( - this.state.selectedOS == 'hp' || - this.state.selectedOS == 'sol' || - this.state.selectedOS == 'alpine' - ) - ? [ - { - title: 'Wazuh server address', - children: ( - - - - ), - }, - ] - : []), - ...(!( - !this.state.needsPassword || - this.state.hidePasswordInput || - !!['solaris10', 'solaris11', '11.31', '3.12.12'].includes( - this.state.selectedVersion, - ) - ) - ? [ - { - title: 'Wazuh password', - children: {passwordInput}, - }, - ] - : []), - ...(!( - this.state.selectedOS == 'hp' || - this.state.selectedOS == 'sol' || - this.state.selectedOS == 'alpine' - ) - ? [ - { - title: 'Optional settings', - children: ( - - {agentName} - {groupInput} - {agentGroup} - - ), - }, - ] - : []), - { - title: 'Install and enroll the agent', - children: this.state.gotErrorRegistrationServiceInfo ? ( - calloutErrorRegistrationServiceInfo - ) : this.state.agentNameError && - !['hp', 'sol', 'alpine'].includes(this.state.selectedOS) ? ( - - ) : missingOSSelection.length ? ( - - ) : ( -
{guide}
- ), - }, - ...(this.state.selectedOS == 'rpm' || - this.state.selectedOS == 'cent' || - this.state.selectedOS == 'suse' || - this.state.selectedOS == 'fedora' || - this.state.selectedOS == 'oraclelinux' || - this.state.selectedOS == 'amazonlinux' || - this.state.selectedOS == 'deb' || - this.state.selectedOS == 'raspbian' || - this.state.selectedOS == 'ubu' || - this.state.selectedOS == 'win' || - this.state.selectedOS == 'macos' || - this.state.selectedOS == 'open' || - this.state.selectedOS == 'sol' || - this.state.selectedOS == 'aix' || - this.state.selectedOS == 'hp' || - this.state.selectedOS == 'alpine' || - this.state.selectedOS == '' - ? [ - { - title: 'Start the agent', - children: this.state.gotErrorRegistrationServiceInfo ? ( - calloutErrorRegistrationServiceInfo - ) : this.state.agentNameError && - !['hp', 'sol', 'alpine'].includes(this.state.selectedOS) ? ( - - ) : missingOSSelection.length ? ( - - ) : ( - - ), - }, - ] - : []), - ...(!missingOSSelection.length && - this.state.selectedOS !== 'rpm' && - this.state.selectedOS !== 'deb' && - this.state.selectedOS !== 'cent' && - this.state.selectedOS !== 'ubu' && - this.state.selectedOS !== 'win' && - this.state.selectedOS !== 'macos' && - this.state.selectedOS !== 'open' && - this.state.selectedOS !== 'sol' && - this.state.selectedOS !== 'aix' && - this.state.selectedOS !== 'hp' && - this.state.selectedOS !== 'amazonlinux' && - this.state.selectedOS !== 'fedora' && - this.state.selectedOS !== 'oraclelinux' && - this.state.selectedOS !== 'suse' && - this.state.selectedOS !== 'raspbian' && - this.state.selectedOS !== 'alpine' && - restartAgentCommand - ? [ - { - title: 'Start the agent', - children: this.state.gotErrorRegistrationServiceInfo ? ( - calloutErrorRegistrationServiceInfo - ) : ( - - -
- - {restartAgentCommand} - - - {copy => ( -
-

- Copy command -

-
- )} -
-
-
-
- ), - }, - ] - : []), - ]; - - return ( -
- - - - - - - - -

Deploy a new agent

-
-
- - {this.props.hasAgents() && ( - this.props.addNewAgent(false)} - iconType='cross' - > - Close - - )} - {!this.props.hasAgents() && ( - this.props.reload()} - iconType='refresh' - > - Refresh - - )} - -
- - {this.state.loading && ( - <> - - - - - - )} - {!this.state.loading && ( - - - - )} -
-
-
-
-
-
- ); - } - }, -); diff --git a/plugins/main/public/controllers/agent/components/register-agent.scss b/plugins/main/public/controllers/agent/components/register-agent.scss deleted file mode 100644 index e19973cad7..0000000000 --- a/plugins/main/public/controllers/agent/components/register-agent.scss +++ /dev/null @@ -1,14 +0,0 @@ -.registerAgent{ - min-height: calc(100vh - 100px); - background: #fafbfd; - .euiButtonGroup__buttons { - display: grid; - grid-template-columns: repeat(5, 1fr); - grid-gap: 10px; - padding: 0 3px; - box-shadow: none; - } - .euiButtonGroup--medium .euiButtonGroupButton:not(:first-child), .euiButtonGroup--small .euiButtonGroupButton:not(:first-child) { - margin-left: 0 !important; - } -} \ No newline at end of file diff --git a/plugins/main/public/controllers/agent/components/register-agent.test.js b/plugins/main/public/controllers/agent/components/register-agent.test.js deleted file mode 100644 index 9d8b76ff23..0000000000 --- a/plugins/main/public/controllers/agent/components/register-agent.test.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { render, waitFor } from '@testing-library/react'; -import { RegisterAgent } from '../components/register-agent' - -// mocked getErrorOrchestrator -const mockedGetErrorOrchestrator = { - handleError: jest.fn(), - }; - - jest.mock('../../../react-services/common-services', () => { - return { - getErrorOrchestrator: () => mockedGetErrorOrchestrator, - }; - }); - -describe('RegisterAgent', () => { - it('mount - RegisterAgent', async() => { - const getWazuhVersion = jest.fn(); - const getCurrentApiAddress = jest.fn(); - const addNewAgent = jest.fn(); - const reload = jest.fn(); - - const props = { - hasAgents: true, - getWazuhVersion, - getCurrentApiAddress, - addNewAgent, - reload, - }; - const { debug } = render(); - debug() - }); -}); diff --git a/plugins/main/public/controllers/agent/components/wz-accordion.tsx b/plugins/main/public/controllers/agent/components/wz-accordion.tsx deleted file mode 100644 index f2e72ed167..0000000000 --- a/plugins/main/public/controllers/agent/components/wz-accordion.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState } from 'react'; -import { - EuiPanel, - EuiSpacer, - EuiAccordion, - EuiButtonGroup, - htmlIdGenerator, -} from '@elastic/eui'; -import { osButtons } from '../wazuh-config'; - -export const PrincipalButtonGroup = ({ - legend, - options, - idSelected, - onChange, -}) => { - return ( - <> - - - - - - - ); -}; - -export const WzAccordion = ({ children }) => { - const [isAccordionOpen, setIsAccordionOpen] = useState(false); - const rightArrowAccordionId = htmlIdGenerator('wz-accordion')(); - return ( - setIsAccordionOpen(isOpen)} - className={'action-btn-td'} - > - - - {children} - - - ); -}; diff --git a/plugins/main/public/controllers/agent/index.js b/plugins/main/public/controllers/agent/index.js index 51a445bb00..960ac79295 100644 --- a/plugins/main/public/controllers/agent/index.js +++ b/plugins/main/public/controllers/agent/index.js @@ -9,30 +9,28 @@ * * Find more information about this on the LICENSE file. */ -import { AgentsPreviewController } from './agents-preview'; import { AgentsController } from './agents'; -import { RegisterAgent } from '../../controllers/register-agent/containers/register-agent/register-agent'; +import { RegisterAgent } from '../../components/endpoints-summary/register-agent/containers/register-agent/register-agent'; import { ExportConfiguration } from './components/export-configuration'; import { AgentsWelcome } from '../../components/common/welcome/agents-welcome'; import { Mitre } from '../../components/overview'; -import { AgentsPreview } from './components/agents-preview'; -import { AgentsTable } from './components/agents-table'; +import { AgentsTable } from '../../components/endpoints-summary/table/agents-table'; import { MainModule } from '../../components/common/modules/main'; import { MainSyscollector } from '../../components/agents/syscollector/main'; import { MainAgentStats } from '../../components/agents/stats'; import { getAngularModule } from '../../kibana-services'; +import { MainEndpointsSummary } from '../../components/endpoints-summary'; const app = getAngularModule(); app .controller('agentsController', AgentsController) - .controller('agentsPreviewController', AgentsPreviewController) .value('RegisterAgent', RegisterAgent) .value('ExportConfiguration', ExportConfiguration) .value('AgentsWelcome', AgentsWelcome) - .value('AgentsPreview', AgentsPreview) .value('Mitre', Mitre) .value('AgentsTable', AgentsTable) .value('MainSyscollector', MainSyscollector) .value('MainAgentStats', MainAgentStats) - .value('MainModule', MainModule); + .value('MainModule', MainModule) + .value('MainEndpointsSummary', MainEndpointsSummary); diff --git a/plugins/main/public/controllers/agent/register-agent/__snapshots__/register-agent-button-group.test.tsx.snap b/plugins/main/public/controllers/agent/register-agent/__snapshots__/register-agent-button-group.test.tsx.snap deleted file mode 100644 index 2d214a76d1..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/__snapshots__/register-agent-button-group.test.tsx.snap +++ /dev/null @@ -1,138 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RegisterAgentButtonGroup should render correctly 1`] = ` -Object { - "asFragment": [Function], - "baseElement": -
-
- - Test legend - -
- -
-
-
- , - "container":
-
- - Test legend - -
- -
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/plugins/main/public/controllers/agent/register-agent/register-agent-button-group.test.tsx b/plugins/main/public/controllers/agent/register-agent/register-agent-button-group.test.tsx deleted file mode 100644 index 88c8582390..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/register-agent-button-group.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from 'react'; -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import RegisterAgentButtonGroup from './register-agent-button-group'; - -jest.mock( - '../../../../../../node_modules/@elastic/eui/lib/services/accessibility/html_id_generator', - () => ({ - htmlIdGenerator: () => () => 'htmlId', - }) -); - - -describe('RegisterAgentButtonGroup', () => { - it('should render correctly', () => { - const buttonsOpts = [{ id: 'test', label: 'test' }]; - const wrapper = render( - {}} - />, - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('should render the label text', () => { - const buttonsOpts = [{ id: 'test', label: 'test' }]; - const { getByText } = render( - {}} - />, - ); - expect(getByText('Test legend')).toBeInTheDocument(); - }); - - it('should auto select button when the group have only one button in options list', () => { - const mockedOnChange = jest.fn(); - const buttonsOpts = [{ id: 'test', label: 'test' }]; - render( - , - ); - expect(mockedOnChange).toBeCalledWith(buttonsOpts[0].id); - }); - - it('should auto select button when the group have an option with the default property in true', () => { - const mockedOnChange = jest.fn(); - const buttonsOpts = [ - { id: 'test', label: 'test' }, - { id: 'auto-selected', label: 'test2', default: true }, - ]; - render( - , - ); - expect(mockedOnChange).toBeCalledWith(buttonsOpts[1].id); - }); - - it('should auto select the first button when the group have more than one option with the default property in true', () => { - const mockedOnChange = jest.fn(); - const buttonsOpts = [ - { id: 'test', label: 'test' }, - { id: 'auto-selected', label: 'test2', default: true }, - { id: 'auto-selected2', label: 'test3', default: true }, - ]; - render( - , - ); - expect(mockedOnChange).toBeCalledWith(buttonsOpts[1].id); - }); - - it('should render the correct number of buttons defined in the options received', () => { - const mockedOnChange = jest.fn(); - const buttonsOpts = [ - { id: 'test', label: 'test' }, - { id: 'test2', label: 'test2' }, - { id: 'test3', label: 'test3' }, - ]; - const { getByText, getByRole } = render( - , - ); - - buttonsOpts.forEach((button) => { - expect(getByText(button.label)).toBeInTheDocument(); - }) - }); - -}); diff --git a/plugins/main/public/controllers/agent/register-agent/register-agent-button-group.tsx b/plugins/main/public/controllers/agent/register-agent/register-agent-button-group.tsx deleted file mode 100644 index bf7b40546e..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/register-agent-button-group.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { EuiButtonGroup } from '@elastic/eui'; -import React, { useEffect } from 'react'; - -interface RegisterAgentButtonGroupProps { - legend: string; - options: Array<{id: string, label: string, default?: boolean}>; - idSelected: string; - onChange: (value: string) => void; -} - -export default function RegisterAgentButtonGroup({ - legend, - options, - idSelected, - onChange, -}: RegisterAgentButtonGroupProps) { - - useEffect(() => { - setDefaultOptions(); - }, []); - - useEffect(() => { - setDefaultOptions(); - }, [options, idSelected]) - - /** - * Set default option - * Autoselect option when there is only one option - * Autoselect option when an option have default property in true - */ - const setDefaultOptions = () => { - if(!idSelected){ // prevent autoselect every time the options change - if (options.length === 1) { - idSelected = options[0].id; - onChange(options[0].id); - }else if(options.filter(item => item.default).length > 0){ - const defaultOption = options.filter(item => item.default)[0].id - idSelected = defaultOption; - onChange(defaultOption); - } - } - }; - - return ( - - ); -} diff --git a/plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap b/plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap deleted file mode 100644 index 5326643f51..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/steps/__snapshots__/server-address.test.tsx.snap +++ /dev/null @@ -1,84 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Server Address Combobox should match snapshot 1`] = ` -
-
-

- This is the address the agent uses to communicate with the Wazuh server. It can be an IP address or a fully qualified domain name (FQDN). -

- -
-`; diff --git a/plugins/main/public/controllers/agent/register-agent/steps/index.ts b/plugins/main/public/controllers/agent/register-agent/steps/index.ts deleted file mode 100644 index 5732aed9ae..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/steps/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './server-address'; \ No newline at end of file diff --git a/plugins/main/public/controllers/agent/register-agent/steps/server-address.test.tsx b/plugins/main/public/controllers/agent/register-agent/steps/server-address.test.tsx deleted file mode 100644 index ce3e8c513e..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/steps/server-address.test.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { act } from 'react-dom/test-utils'; -import ServerAddress from './server-address'; -import * as registerAgentsUtils from '../../components/register-agent-service'; - -jest.mock('../../../../kibana-services', () => ({ - ...(jest.requireActual('../../../../kibana-services') as object), - getHttp: jest.fn().mockReturnValue({ - basePath: { - get: () => { - return 'http://localhost:5601'; - }, - prepend: url => { - return `http://localhost:5601${url}`; - }, - }, - }), - getCookies: jest.fn().mockReturnValue({ - set: (name, value, options) => { - return true; - }, - get: () => { - return '{}'; - }, - remove: () => { - return; - }, - }), -})); - -const mockedNodesIps = [ - { - name: 'master-node', - type: 'master', - version: '4.x', - ip: 'wazuh-master', - }, - { - name: 'worker1', - type: 'worker', - version: '4.x', - ip: '172.26.0.7', - }, - { - name: 'worker2', - type: 'worker', - version: '4.x', - ip: '172.26.0.6', - }, -]; - -const mockedClusterNodes = { - data: { - data: { - affected_items: mockedNodesIps, - total_affected_items: mockedNodesIps.length, - total_failed_items: 0, - failed_items: [], - }, - message: 'All selected nodes information was returned', - error: 0, - }, -}; - -const promiseFetchOptions = Promise.resolve( - registerAgentsUtils.parseNodesInOptions(mockedClusterNodes), -); -const mockedFetchOptions = () => promiseFetchOptions; - -describe('Server Address Combobox', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should render correctly', () => { - const { container } = render( - {}} - fetchOptions={() => - Promise.resolve( - registerAgentsUtils.parseNodesInOptions(mockedClusterNodes), - ) - } - />, - ); - expect(container).toBeInTheDocument(); - }); - - it('should match snapshot', () => { - const { container } = render( - {}} fetchOptions={mockedFetchOptions} />, - ); - expect(container).toMatchSnapshot(); - }); - - it('should set default combobox value and disable input when defaultValue is defined', async () => { - const onChangeMocked = jest.fn(); - const { container, getByText, getByRole } = render( - , - ); - - await act(async () => { - await promiseFetchOptions; - expect(onChangeMocked).toBeCalledTimes(1); - expect(onChangeMocked).toBeCalledWith([ - { label: 'default-dns', value: 'default-dns', nodetype: 'custom' }, - ]); - expect(getByText('default-dns')).toBeInTheDocument(); - expect(getByRole('textbox')).toHaveAttribute('disabled'); - expect(container).toBeInTheDocument(); - }); - }); - - it('should set node type master like default value when combobox is initiliazed and not have defaultValue', async () => { - const { container, getByText } = render( - {}} fetchOptions={mockedFetchOptions} />, - ); - - await act(async () => { - await promiseFetchOptions; // waiting for the combobox items are loaded - expect(getByText('master-node')).toBeInTheDocument(); - expect(container).toBeInTheDocument(); - }); - }); - - it('should render the correct number of options', async () => { - const { getByRole, findByText } = render( - {}} fetchOptions={mockedFetchOptions} />, - ); - - await act(async () => { - await promiseFetchOptions; // waiting for the combobox items are loaded - fireEvent.click(getByRole('button', { name: 'Clear input' })); - await findByText(`${mockedNodesIps[0].name}:${mockedNodesIps[0].ip}`); - await findByText(`${mockedNodesIps[1].name}:${mockedNodesIps[1].ip}`); - await findByText(`${mockedNodesIps[2].name}:${mockedNodesIps[2].ip}`); - }); - }); - - it('should allow only single selection', async () => { - const onChangeMocked = jest.fn(); - const { getByRole, getByText, findByText } = render( - , - ); - await act(async () => { - await promiseFetchOptions; // waiting for the combobox items are loaded - fireEvent.click(getByRole('button', { name: 'Clear input' })); - const serverAddresInput = getByRole('textbox'); - fireEvent.change(serverAddresInput, { target: { value: 'first-typed' } }); - fireEvent.keyDown(serverAddresInput, { key: 'Enter', code: 'Enter' }); - fireEvent.change(serverAddresInput, { target: { value: 'last-typed' } }); - fireEvent.keyDown(serverAddresInput, { key: 'Enter', code: 'Enter' }); - expect(onChangeMocked).toHaveBeenLastCalledWith([ - { label: 'last-typed', value: 'last-typed', nodetype: 'custom' }, - ]); - expect(getByText('last-typed')).toBeInTheDocument(); - }); - }); - - it('should return EMPTY parsed Node IPs when options are not selected', async () => { - const onChangeMocked = jest.fn(); - const { getByRole, container } = render( - , - ); - await act(async () => { - await promiseFetchOptions; // waiting for the combobox items are loaded - fireEvent.click(getByRole('button', { name: 'Clear input' })); - expect(onChangeMocked).toBeCalledTimes(2); - expect(onChangeMocked).toBeCalledWith([]); - expect(container).toBeInTheDocument(); - }); - }); - - it('should allow create customs options when user type and trigger enter key', async () => { - const onChangeMocked = jest.fn(); - - const { getByRole } = render( - , - ); - await act(async () => { - await promiseFetchOptions; // waiting for the combobox items are loaded - fireEvent.change(getByRole('textbox'), { - target: { value: 'custom-ip-dns' }, - }); - fireEvent.keyDown(getByRole('textbox'), { key: 'Enter', code: 'Enter' }); - expect(onChangeMocked).toBeCalledTimes(2); - expect(onChangeMocked).toHaveBeenNthCalledWith(2, [ - { label: 'custom-ip-dns', value: 'custom-ip-dns', nodetype: 'custom' }, - ]); - }); - }); - - it('should show "node.name:node.ip" in the combobox options', async () => { - const { getByRole, getByText } = render( - {}} fetchOptions={mockedFetchOptions} />, - ); - await act(async () => { - await promiseFetchOptions; // waiting for the combobox items are loaded - fireEvent.click(getByRole('button', { name: 'Clear input' })); - }); - - mockedNodesIps.forEach(nodeItem => { - expect(getByText(`${nodeItem.name}:${nodeItem.ip}`)).toBeInTheDocument(); - }); - }); -}); diff --git a/plugins/main/public/controllers/agent/register-agent/steps/server-address.tsx b/plugins/main/public/controllers/agent/register-agent/steps/server-address.tsx deleted file mode 100644 index 8c23073f36..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/steps/server-address.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { - EuiComboBox, - EuiComboBoxOptionOption, - EuiHighlight, - EuiText, -} from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; -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 { getMasterNode } from '../../components/register-agent-service'; - -type Props = { - onChange: (value: EuiComboBoxOptionOption[]) => void; - fetchOptions: () => Promise[]>; - defaultValue?: string; -}; - -export type ServerAddressOptions = EuiComboBoxOptionOption & { - nodetype?: string; -}; - -const ServerAddress = (props: Props) => { - const { onChange, fetchOptions, defaultValue } = props; - const [nodeIPs, setNodeIPs] = useState([]); - const [selectedNodeIPs, setSelectedNodeIPs] = useState< - ServerAddressOptions[] - >([]); - const [isLoading, setIsLoading] = useState(false); - const [isDisabled, setIsDisabled] = useState(false); - - useEffect(() => { - initialize(); - }, []); - - /** - * Fetches the node IPs (options) and sets the state - */ - const initialize = async () => { - if (!fetchOptions) { - throw new Error('fetchOptions is required'); - } - try { - setIsLoading(true); - await setDefaultValue(); - setIsLoading(false); - } catch (error) { - setIsLoading(false); - const options = { - context: `${ServerAddress.name}.initialize`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - display: true, - store: false, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - getErrorOrchestrator().handleError(options); - } - }; - - /** - * Sets the default value of server address - */ - const setDefaultValue = async () => { - if (defaultValue) { - const defaultNode = [{ label: defaultValue, value: defaultValue, nodetype: 'custom' }]; - handleOnChange(defaultNode); - setIsDisabled(true); - } else { - setIsDisabled(false); - const nodeIps = await fetchOptions(); - setNodeIPs(nodeIps); - const defaultNode = getMasterNode(nodeIps); - if (defaultNode.length > 0) { - handleOnChange(defaultNode); - } - } - }; - - /** - * Handles the change of the selected node IP - * @param value - */ - const handleOnChange = (value: EuiComboBoxOptionOption[]) => { - setSelectedNodeIPs(value); - onChange(value); - }; - - /** - * Handle the render of the custom options in the combobox list - * @param option - * @param searchValue - * @param contentClassName - */ - const handleRenderOption = ( - option: EuiComboBoxOptionOption, - inputValue: string, - contentClassName: string, - ) => { - const { label, value } = option; - return ( - - {`${label}:${value}`} - - ); - }; - - /** - * Handle the interaction when the user enter a option that is not in the list - * Creating new options in the list and selecting it - * @param inputValue - * @param options - */ - const handleOnCreateOption = ( - inputValue: string, - options: ServerAddressOptions[] = [], - ) => { - if (!inputValue) { - return; - } - - const normalizedSearchValue = inputValue.trim().toLowerCase(); - if (!normalizedSearchValue) { - return; - } - - const newOption = { - value: inputValue, - label: inputValue, - nodetype: 'custom', - }; - // Create the option if it doesn't exist. - if ( - options.findIndex( - (option: ServerAddressOptions) => - option.label.trim().toLowerCase() === normalizedSearchValue, - ) === -1 - ) { - setNodeIPs([...nodeIPs, newOption]); - } - // Select the option. - handleOnChange([newOption]); - }; - - return ( - -

- This is the address the agent uses to communicate with the Wazuh server. It can be an IP address or a fully qualified domain name (FQDN). -

- handleOnCreateOption(sv, fo)} - /> -
- ); -}; - -export default ServerAddress; diff --git a/plugins/main/public/controllers/agent/register-agent/steps/wz-manager-address.tsx b/plugins/main/public/controllers/agent/register-agent/steps/wz-manager-address.tsx deleted file mode 100644 index 8bfd679e2f..0000000000 --- a/plugins/main/public/controllers/agent/register-agent/steps/wz-manager-address.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { memo, useCallback, useEffect, useState } from 'react'; -import { EuiText, EuiFieldText } from '@elastic/eui'; - -type Props = { - onChange: (value: string) => void; - defaultValue?: string; -}; - -const WzManagerAddressInput = (props: Props) => { - const { onChange, defaultValue } = props; - const [value, setValue] = useState(''); - - useEffect(() => { - if (defaultValue) { - setValue(defaultValue); - onChange(defaultValue); - } else { - setValue(''); - onChange(''); - } - }, []); - /** - * Handles the change of the selected node IP - * @param value - */ - const handleOnChange = (event: React.ChangeEvent) => { - const { value } = event.target; - onChange(value); - setValue(value); - }; - return ( - -

- This is the address the agent uses to communicate with the Wazuh server. - It can be an IP address or a fully qualified domain name (FQDN). -

- -
- ); -}; - -export default WzManagerAddressInput; diff --git a/plugins/main/public/controllers/agent/wazuh-config/index.ts b/plugins/main/public/controllers/agent/wazuh-config/index.ts deleted file mode 100644 index 70b345bf8e..0000000000 --- a/plugins/main/public/controllers/agent/wazuh-config/index.ts +++ /dev/null @@ -1,420 +0,0 @@ -const architectureButtons = [ - { - id: 'i386', - label: 'i386', - }, - { - id: 'x86_64', - label: 'x86_64', - default: true, - }, - { - id: 'armhf', - label: 'armhf', - }, - { - id: 'aarch64', - label: 'aarch64', - }, -]; - -const architectureButtonsWithPPC64LE = [ - { - id: 'i386', - label: 'i386', - }, - { - id: 'x86_64', - label: 'x86_64', - default: true, - }, - { - id: 'armhf', - label: 'armhf', - }, - { - id: 'aarch64', - label: 'aarch64', - }, - { - id: 'powerpc', - label: 'PowerPC', - }, -]; - -const architectureButtonsWithPPC64LEAlpine = [ - { - id: 'i386', - label: 'i386', - }, - { - id: 'x86', - label: 'x86', - }, - { - id: 'x86_64', - label: 'x86_64', - default: true, - }, - { - id: 'armhf', - label: 'armhf', - }, - { - id: 'aarch64', - label: 'aarch64', - }, - { - id: 'powerpc', - label: 'PowerPC', - }, -]; - -const architectureButtonsi386 = [ - { - id: 'i386/x86_64', - label: 'i386/x86_64', - }, -]; - -const architecturei386Andx86_64 = [ - { - id: 'i386', - label: 'i386', - }, - { - id: 'x86_64', - label: 'x86_64', - default: true, - }, -]; - -const architectureButtonsSolaris = [ - { - id: 'i386', - label: 'i386', - default: true, - }, - { - id: 'sparc', - label: 'SPARC', - }, -]; - -const architectureButtonsMacos = [ - { - id: 'intel64', - label: 'Intel', - }, - { - id: 'arm64', - label: 'Apple silicon', - }, -]; - -const architectureButtonsAix = [ - { - id: 'powerpc', - label: 'PowerPC', - }, -]; - -const architectureButtonsHpUx = [ - { - id: 'itanium2', - label: 'Itanium2', - }, -]; - -const versionButtonAmazonLinux = [ - { - id: 'amazonlinux1', - label: 'Amazon Linux 1', - }, - { - id: 'amazonlinux2', - label: 'Amazon Linux 2', - }, - { - id: 'amazonlinux2022', - label: 'Amazon Linux 2022', - default: true, - }, -]; - -const versionButtonsRedHat = [ - { - id: 'redhat5', - label: 'Red Hat 5', - }, - { - id: 'redhat6', - label: 'Red Hat 6', - }, - { - id: 'redhat7', - label: 'Red Hat 7 +', - default: true, - }, -]; - -const versionButtonsCentos = [ - { - id: 'centos5', - label: 'CentOS 5', - }, - { - id: 'centos6', - label: 'CentOS 6', - }, - { - id: 'centos7', - label: 'CentOS 7 +', - default: true, - }, -]; - -const versionButtonsDebian = [ - { - id: 'debian7', - label: 'Debian 7', - }, - { - id: 'debian8', - label: 'Debian 8', - }, - { - id: 'debian9', - label: 'Debian 9 +', - default: true, - }, -]; - -const versionButtonFedora = [ - { - id: '22', - label: 'Fedora 22 +', - }, -]; - -const versionButtonsUbuntu = [ - { - id: 'ubuntu14', - label: 'Ubuntu 14', - }, - { - id: 'ubuntu15', - label: 'Ubuntu 15 +', - default: true, - }, -]; - -const versionButtonsWindows = [ - { - id: 'windowsxp', - label: 'Windows XP', - }, - { - id: 'windowsserver2008', - label: 'Windows Server 2008', - }, - { - id: 'windows7', - label: 'Windows 7 +', - default: true, - }, -]; - -const versionButtonsSuse = [ - { - id: 'suse11', - label: 'SUSE 11', - }, - { - id: 'suse12', - label: 'SUSE 12', - default: true, - }, -]; - -const versionButtonsMacOS = [ - { - id: 'sierra', - label: 'macOS Sierra +', - }, -]; - -const versionButtonsOpenSuse = [ - { - id: 'leap15', - label: 'openSUSE Leap 15 +', - }, -]; - -const versionButtonsSolaris = [ - { - id: 'solaris10', - label: 'Solaris 10', - }, - { - id: 'solaris11', - label: 'Solaris 11', - default: true, - }, -]; - -const versionButtonsAix = [ - { - id: '6.1 TL9', - label: 'AIX 6.1 TL9 +', - }, -]; - -const versionButtonsHPUX = [ - { - id: '11.31', - label: 'HP-UX 11.31 +', - }, -]; - -const versionButtonsOracleLinux = [ - { - id: 'oraclelinux5', - label: 'Oracle Linux 5', - }, - { - id: 'oraclelinux6', - label: 'Oracle Linux 6 +', - default: true, - }, -]; - -const versionButtonsRaspbian = [ - { - id: 'busterorgreater', - label: 'Raspbian Buster or greater', - }, -]; - -const versionButtonAlpine = [ - { - id: '3.12.12', - label: '3.12.12 +', - }, -]; - -/** - * Order the OS Buttons Alphabetically by label - * @param a - * @param b - * @returns - */ -const orderOSAlphabetically = (a, b) => { - if (a.label.toUpperCase() < b.label.toUpperCase()) { - return -1; - } - if (a.label.toUpperCase() > b.label.toUpperCase()) { - return 1; - } - return 0; -}; - -const osPrincipalButtons = [ - { - id: 'rpm', - label: 'Red Hat Enterprise Linux', - }, - { - id: 'cent', - label: 'CentOS', - }, - { - id: 'ubu', - label: 'Ubuntu', - }, - { - id: 'win', - label: 'Windows', - }, - { - id: 'macos', - label: 'macOS', - }, -]; - -const osButtons = [ - { - id: 'deb', - label: 'Debian', - }, - { - id: 'open', - label: 'openSUSE', - }, - { - id: 'sol', - label: 'Solaris', - }, - { - id: 'aix', - label: 'AIX', - }, - { - id: 'hp', - label: 'HP-UX', - }, - { - id: 'amazonlinux', - label: 'Amazon Linux', - }, - { - id: 'fedora', - label: 'Fedora', - }, - { - id: 'oraclelinux', - label: 'Oracle Linux', - }, - { - id: 'suse', - label: 'SUSE', - }, - { - id: 'raspbian', - label: 'Raspbian OS', - }, - { - id: 'alpine', - label: 'Alpine', - }, -].sort(orderOSAlphabetically); - -export { - architectureButtons, - architecturei386Andx86_64, - versionButtonsRaspbian, - versionButtonsSuse, - architectureButtonsWithPPC64LE, - versionButtonsOracleLinux, - versionButtonFedora, - versionButtonsRedHat, - versionButtonsCentos, - versionButtonAlpine, - architectureButtonsMacos, - osButtons, - osPrincipalButtons, - versionButtonsDebian, - versionButtonsUbuntu, - versionButtonAmazonLinux, - versionButtonsWindows, - versionButtonsMacOS, - versionButtonsOpenSuse, - versionButtonsSolaris, - versionButtonsAix, - versionButtonsHPUX, - architectureButtonsi386, - architectureButtonsSolaris, - architectureButtonsAix, - architectureButtonsHpUx, - architectureButtonsWithPPC64LEAlpine, -}; diff --git a/plugins/main/public/controllers/register-agent/containers/register-agent/register-agent.tsx b/plugins/main/public/controllers/register-agent/containers/register-agent/register-agent.tsx deleted file mode 100644 index 8ae23213cd..0000000000 --- a/plugins/main/public/controllers/register-agent/containers/register-agent/register-agent.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiTitle, - EuiButtonEmpty, - EuiPage, - EuiPageBody, - EuiSpacer, - EuiProgress, - EuiButton, -} from '@elastic/eui'; -import { WzRequest } from '../../../../react-services/wz-request'; -import { UI_LOGGER_LEVELS } from '../../../../../common/constants'; -import { UI_ERROR_SEVERITIES } from '../../../../react-services/error-orchestrator/types'; -import { ErrorHandler } from '../../../../react-services/error-management'; -import { getMasterRemoteConfiguration } from '../../../agent/components/register-agent-service'; -import './register-agent.scss'; -import { Steps } from '../steps/steps'; -import { InputForm } from '../../../../components/common/form'; -import { getGroups } from '../../services/register-agent-services'; -import { useForm } from '../../../../components/common/form/hooks'; -import { FormConfiguration } from '../../../../components/common/form/types'; -import { useSelector } from 'react-redux'; -import { withReduxProvider } from '../../../../components/common/hocs'; -import GroupInput from '../../components/group-input/group-input'; -import { OsCard } from '../../components/os-selector/os-card/os-card'; -import { - validateServerAddress, - validateAgentName, -} from '../../utils/validations'; - -interface IRegisterAgentProps { - getWazuhVersion: () => Promise; - hasAgents: () => Promise; - addNewAgent: (agent: any) => Promise; - reload: () => void; -} - -export const RegisterAgent = withReduxProvider( - ({ - getWazuhVersion, - hasAgents, - addNewAgent, - reload, - }: IRegisterAgentProps) => { - const configuration = useSelector( - (state: { appConfig: { data: any } }) => state.appConfig.data, - ); - const [wazuhVersion, setWazuhVersion] = useState(''); - const [haveUdpProtocol, setHaveUdpProtocol] = useState( - false, - ); - const [loading, setLoading] = useState(false); - const [wazuhPassword, setWazuhPassword] = useState(''); - const [groups, setGroups] = useState([]); - const [needsPassword, setNeedsPassword] = useState(false); - - const initialFields: FormConfiguration = { - operatingSystemSelection: { - type: 'custom', - initialValue: '', - component: props => { - return ; - }, - options: { - groups, - }, - }, - serverAddress: { - type: 'text', - initialValue: configuration['enrollment.dns'] || '', - validate: validateServerAddress, - }, - agentName: { - type: 'text', - initialValue: '', - validate: validateAgentName, - }, - - agentGroups: { - type: 'custom', - initialValue: [], - component: props => { - return ; - }, - options: { - groups, - }, - }, - }; - - const form = useForm(initialFields); - - const getRemoteConfig = async () => { - const remoteConfig = await getMasterRemoteConfiguration(); - if (remoteConfig) { - setHaveUdpProtocol(remoteConfig.isUdp); - } - }; - - const getAuthInfo = async () => { - try { - const result = await WzRequest.apiReq( - 'GET', - '/agents/000/config/auth/auth', - {}, - ); - return (result.data || {}).data || {}; - } catch (error) { - ErrorHandler.handleError(error); - } - }; - - useEffect(() => { - const fetchData = async () => { - try { - const wazuhVersion = await getWazuhVersion(); - await getRemoteConfig(); - const authInfo = await getAuthInfo(); - // get wazuh password configuration - let wazuhPassword = ''; - const needsPassword = (authInfo.auth || {}).use_password === 'yes'; - if (needsPassword) { - wazuhPassword = - configuration['enrollment.password'] || - authInfo['authd.pass'] || - ''; - } - const groups = await getGroups(); - setNeedsPassword(needsPassword); - setWazuhPassword(wazuhPassword); - setWazuhVersion(wazuhVersion); - setGroups(groups); - setLoading(false); - } catch (error) { - setWazuhVersion(wazuhVersion); - setLoading(false); - const options = { - context: 'RegisterAgent', - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - display: true, - store: false, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - ErrorHandler.handleError(error, options); - } - }; - - fetchData(); - }, []); - - const osCard = ( - - ); - - return ( -
- - - - - -
- {hasAgents() ? ( - addNewAgent(false)} - iconType='cross' - > - Close - - ) : ( - reload()} - iconType='refresh' - > - Refresh - - )} -
- - - -

- Deploy new agent -

-
-
-
- - {loading ? ( - <> - - - - - - ) : ( - - - - )} - - - reload()} - > - Close - - - -
-
-
-
-
-
- ); - }, -); diff --git a/plugins/main/public/services/routes.js b/plugins/main/public/services/routes.js index 835b81ab2f..06107272ca 100644 --- a/plugins/main/public/services/routes.js +++ b/plugins/main/public/services/routes.js @@ -20,6 +20,7 @@ import { settingsWizard, getSavedSearch, getIp, getWzConfig } from './resolves'; // HTML templates import healthCheckTemplate from '../templates/health-check/health-check.html'; import agentsTemplate from '../templates/agents/dashboards.html'; +import agentDeployTemplate from '../templates/agents/deploy/agent-deploy.html'; import agentsPrevTemplate from '../templates/agents-prev/agents-prev.html'; import managementTemplate from '../templates/management/management.html'; import overviewTemplate from '../templates/visualize/dashboards.html'; @@ -109,6 +110,12 @@ app.config($routeProvider => { resolve: { wzConfig, ip }, outerAngularWrapperRoute: true, }) + .when('/agents-preview/deploy', { + template: agentDeployTemplate, + resolve: { enableWzMenu, nestedResolve, ip, savedSearch }, + reloadOnSearch: false, + outerAngularWrapperRoute: true, + }) .when('/agents/:agent?/:tab?/:tabView?', { template: agentsTemplate, resolve: { enableWzMenu, nestedResolve, ip, savedSearch }, diff --git a/plugins/main/public/templates/agents-prev/agents-prev.html b/plugins/main/public/templates/agents-prev/agents-prev.html index 573720fea4..30b3efb16c 100644 --- a/plugins/main/public/templates/agents-prev/agents-prev.html +++ b/plugins/main/public/templates/agents-prev/agents-prev.html @@ -1,76 +1 @@ -
-
- -
-
-
-
- - - - Error fetching agents - - -
-

{{ ctrl.errorInit || 'Internal error' }}

-
-
- -
-
-
-
-
- -
-
- -
-
- - -
-
-
+ diff --git a/plugins/main/public/templates/agents/deploy/agent-deploy.html b/plugins/main/public/templates/agents/deploy/agent-deploy.html new file mode 100644 index 0000000000..34c5544c9e --- /dev/null +++ b/plugins/main/public/templates/agents/deploy/agent-deploy.html @@ -0,0 +1 @@ + diff --git a/plugins/main/public/templates/visualize/dashboards.html b/plugins/main/public/templates/visualize/dashboards.html index 1dff0934c2..73bd108f15 100644 --- a/plugins/main/public/templates/visualize/dashboards.html +++ b/plugins/main/public/templates/visualize/dashboards.html @@ -1,138 +1,99 @@ -
-
+
+ +
+
-
-
- -
- - -
-
- - - -
+
+ + + +
- - -
-
-
- - - - - - -
-
{{reportStatus}}
-
-
-
- -
- -
+ + +
+
+
+ + + + + + +
+
{{reportStatus}}
+
+
+
+ +
+ +
-
-
- - - - - - - - - No agents were added to this manager: - - Deploy new agent -
-
- -
-
- -
-
- - -
-
- -
- -
- + +
+
+ +
+
+ + +
+
+
+
+
diff --git a/plugins/main/public/types.ts b/plugins/main/public/types.ts index e80f32877e..d4edcbb66a 100644 --- a/plugins/main/public/types.ts +++ b/plugins/main/public/types.ts @@ -12,6 +12,7 @@ import { SecurityOssPluginStart } from '../../../src/plugins/security_oss/public import { SavedObjectsStart } from '../../../src/plugins/saved_objects/public'; import { TelemetryPluginStart, TelemetryPluginSetup } from '../../../src/plugins/telemetry/public'; import { WazuhCheckUpdatesPluginStart } from '../../wazuh-check-updates/public'; +import { WazuhEndpointsPluginStart } from '../../wazuh-endpoints/public'; import { DashboardStart } from '../../../src/plugins/dashboard/public'; export interface AppPluginStartDependencies { @@ -24,6 +25,7 @@ export interface AppPluginStartDependencies { savedObjects: SavedObjectsStart; telemetry: TelemetryPluginStart; wazuhCheckUpdates: WazuhCheckUpdatesPluginStart; + wazuhEndpoints: WazuhEndpointsPluginStart; dashboard: DashboardStart; } export interface AppDependencies { diff --git a/plugins/wazuh-endpoints/.i18nrc.json b/plugins/wazuh-endpoints/.i18nrc.json new file mode 100644 index 0000000000..57c921787c --- /dev/null +++ b/plugins/wazuh-endpoints/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "wazuhEndpoints", + "paths": { + "wazuhEndpoints": "." + }, + "translations": ["translations/en-US.json"] +} diff --git a/plugins/wazuh-endpoints/common/constants.ts b/plugins/wazuh-endpoints/common/constants.ts new file mode 100644 index 0000000000..ab242f7d67 --- /dev/null +++ b/plugins/wazuh-endpoints/common/constants.ts @@ -0,0 +1,4 @@ +export const PLUGIN_ID = 'wazuhEndpoints'; +export const PLUGIN_NAME = 'wazuh_endpoints'; + +export enum routes {} diff --git a/plugins/wazuh-endpoints/common/types.ts b/plugins/wazuh-endpoints/common/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/wazuh-endpoints/opensearch_dashboards.json b/plugins/wazuh-endpoints/opensearch_dashboards.json new file mode 100644 index 0000000000..37806a8aa4 --- /dev/null +++ b/plugins/wazuh-endpoints/opensearch_dashboards.json @@ -0,0 +1,9 @@ +{ + "id": "wazuhEndpoints", + "version": "4.9.0-00", + "opensearchDashboardsVersion": "opensearchDashboards", + "server": true, + "ui": true, + "requiredPlugins": ["navigation"], + "optionalPlugins": [] +} diff --git a/plugins/wazuh-endpoints/package.json b/plugins/wazuh-endpoints/package.json new file mode 100644 index 0000000000..e79948329b --- /dev/null +++ b/plugins/wazuh-endpoints/package.json @@ -0,0 +1,20 @@ +{ + "name": "wazuh-endpoints", + "version": "4.9.0", + "revision": "00", + "pluginPlatform": { + "version": "2.11.0" + }, + "description": "Wazuh Endpoints", + "private": true, + "scripts": { + "build": "yarn plugin-helpers build --opensearch-dashboards-version=$OPENSEARCH_DASHBOARDS_VERSION", + "plugin-helpers": "node ../../scripts/plugin_helpers", + "osd": "node ../../scripts/osd", + "test:ui:runner": "node ../../scripts/functional_test_runner.js", + "test:server": "plugin-helpers test:server", + "test:browser": "plugin-helpers test:browser", + "test:jest": "node scripts/jest --runInBand", + "test:jest:runner": "node scripts/runner test" + } +} diff --git a/plugins/wazuh-endpoints/public/index.ts b/plugins/wazuh-endpoints/public/index.ts new file mode 100644 index 0000000000..fee1d9b0fa --- /dev/null +++ b/plugins/wazuh-endpoints/public/index.ts @@ -0,0 +1,8 @@ +import { WazuhEndpointsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. +export function plugin() { + return new WazuhEndpointsPlugin(); +} +export { WazuhEndpointsPluginSetup, WazuhEndpointsPluginStart } from './types'; diff --git a/plugins/wazuh-endpoints/public/plugin.ts b/plugins/wazuh-endpoints/public/plugin.ts new file mode 100644 index 0000000000..f2e9e743b3 --- /dev/null +++ b/plugins/wazuh-endpoints/public/plugin.ts @@ -0,0 +1,23 @@ +import { CoreSetup, CoreStart, Plugin } from 'opensearch-dashboards/public'; +import { + AppPluginStartDependencies, + WazuhEndpointsPluginSetup, + WazuhEndpointsPluginStart, +} from './types'; + +export class WazuhEndpointsPlugin + implements Plugin +{ + public setup(core: CoreSetup): WazuhEndpointsPluginSetup { + return {}; + } + + public start( + core: CoreStart, + plugins: AppPluginStartDependencies, + ): WazuhEndpointsPluginStart { + return {}; + } + + public stop() {} +} diff --git a/plugins/wazuh-endpoints/public/types.ts b/plugins/wazuh-endpoints/public/types.ts new file mode 100644 index 0000000000..bb42fe7913 --- /dev/null +++ b/plugins/wazuh-endpoints/public/types.ts @@ -0,0 +1,5 @@ +export interface WazuhEndpointsPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface WazuhEndpointsPluginStart {} + +export interface AppPluginStartDependencies {} diff --git a/plugins/wazuh-endpoints/scripts/jest.js b/plugins/wazuh-endpoints/scripts/jest.js new file mode 100644 index 0000000000..cb58c54ec0 --- /dev/null +++ b/plugins/wazuh-endpoints/scripts/jest.js @@ -0,0 +1,19 @@ +// # Run Jest tests +// +// All args will be forwarded directly to Jest, e.g. to watch tests run: +// +// node scripts/jest --watch +// +// or to build code coverage: +// +// node scripts/jest --coverage +// +// See all cli options in https://facebook.github.io/jest/docs/cli.html + +const path = require('path'); +process.argv.push('--config', path.resolve(__dirname, '../test/jest/config.js')); + +require('../../../src/setup_node_env'); +const jest = require('../../../node_modules/jest'); + +jest.run(process.argv.slice(2)); diff --git a/plugins/wazuh-endpoints/scripts/manifest.js b/plugins/wazuh-endpoints/scripts/manifest.js new file mode 100644 index 0000000000..711059d3ac --- /dev/null +++ b/plugins/wazuh-endpoints/scripts/manifest.js @@ -0,0 +1,17 @@ + +/* eslint-disable @typescript-eslint/no-var-requires */ + +const fs = require('fs'); + +/** + * Reads the package.json file. + * @returns {Object} JSON object. + */ +function loadPackageJson() { + const packageJson = fs.readFileSync('./package.json'); + return JSON.parse(packageJson); +} + +module.exports = { + loadPackageJson +}; \ No newline at end of file diff --git a/plugins/wazuh-endpoints/scripts/runner.js b/plugins/wazuh-endpoints/scripts/runner.js new file mode 100755 index 0000000000..5ba9b132ab --- /dev/null +++ b/plugins/wazuh-endpoints/scripts/runner.js @@ -0,0 +1,148 @@ +/* eslint-disable array-element-newline */ +/* eslint-disable @typescript-eslint/no-var-requires */ + +/** +Runs yarn commands using a Docker container. + +Intended to test and build locally. + +Uses development images. Must be executed from the root folder of the project. + +See /docker/runner/docker-compose.yml for available environment variables. + +# Usage: +# ------------- +# - node scripts/runner [] +# - yarn test:jest:runner [] +# - yarn build:runner +*/ + +const childProcess = require('child_process'); +const { loadPackageJson } = require('./manifest'); + +const COMPOSE_DIR = '../../docker/runner'; + +function getProjectInfo() { + const manifest = loadPackageJson(); + + return { + app: 'osd', + version: manifest['pluginPlatform']['version'], + repo: process.cwd(), + }; +} + +function getBuildArgs({ app, version }) { + return `--opensearch-dashboards-version=${version}`; +} + +/** + * Transforms the Jest CLI options from process.argv back to a string. + * If no options are provided, default ones are generated. + * @returns {String} Space separated string with all Jest CLI options provided. + */ +function getJestArgs() { + // Take args only after `test` word + const index = process.argv.indexOf('test'); + const args = process.argv.slice(index + 1); + // Remove duplicates using set + return Array.from(new Set([...args, '--runInBand'])).join(' '); +} + +/** + * Generates the execution parameters if they are not set. + * @returns {Object} Default environment variables. + */ +const buildEnvVars = ({ app, version, repo, cmd, args }) => { + return { + APP: app, + VERSION: version, + REPO: repo, + CMD: cmd, + ARGS: args, + }; +}; + +/** + * Captures the SIGINT signal (Ctrl + C) to stop the container and exit. + */ +function setupAbortController() { + process.on('SIGINT', () => { + childProcess.spawnSync('docker', [ + 'compose', + '--project-directory', + COMPOSE_DIR, + 'stop', + ]); + process.exit(); + }); +} + +/** + * Start the container. + */ +function startRunner() { + const runner = childProcess.spawn('docker', [ + 'compose', + '--project-directory', + COMPOSE_DIR, + 'up', + ]); + + runner.stdout.on('data', data => { + console.log(`${data}`); + }); + + runner.stderr.on('data', data => { + console.error(`${data}`); + }); +} + +/** + * Main function + */ +function main() { + if (process.argv.length < 2) { + process.stderr.write('Required parameters not provided'); + process.exit(-1); + } + + const projectInfo = getProjectInfo(); + let envVars = {}; + + switch (process.argv[2]) { + case 'build': + envVars = buildEnvVars({ + ...projectInfo, + cmd: 'plugin-helpers build', + args: getBuildArgs({ ...projectInfo }), + }); + break; + + case 'test': + envVars = buildEnvVars({ + ...projectInfo, + cmd: 'test:jest', + args: getJestArgs(), + }); + break; + + default: + // usage(); + console.error('Unsupported or invalid yarn command.'); + process.exit(-1); + } + + // Check the required environment variables are set + for (const [key, value] of Object.entries(envVars)) { + if (!process.env[key]) { + process.env[key] = value; + } + console.log(`${key}: ${process.env[key]}`); + } + + setupAbortController(); + startRunner(); +} + +main(); diff --git a/plugins/wazuh-endpoints/server/index.ts b/plugins/wazuh-endpoints/server/index.ts new file mode 100644 index 0000000000..ac32d575e6 --- /dev/null +++ b/plugins/wazuh-endpoints/server/index.ts @@ -0,0 +1,11 @@ +import { PluginInitializerContext } from '../../../src/core/server'; +import { WazuhEndpointsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new WazuhEndpointsPlugin(initializerContext); +} + +export { WazuhEndpointsPluginSetup, WazuhEndpointsPluginStart } from './types'; diff --git a/plugins/wazuh-endpoints/server/plugin.ts b/plugins/wazuh-endpoints/server/plugin.ts new file mode 100644 index 0000000000..5d238e9787 --- /dev/null +++ b/plugins/wazuh-endpoints/server/plugin.ts @@ -0,0 +1,37 @@ +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from 'opensearch-dashboards/server'; + +import { + PluginSetup, + WazuhEndpointsPluginSetup, + WazuhEndpointsPluginStart, + AppPluginStartDependencies, +} from './types'; + +export class WazuhEndpointsPlugin + implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public async setup(core: CoreSetup, plugins: PluginSetup) { + this.logger.debug('wazuh_endpoints: Setup'); + + return {}; + } + + public start(core: CoreStart, plugins: AppPluginStartDependencies): WazuhEndpointsPluginStart { + this.logger.debug('wazuh_endpoints: Started'); + + return {}; + } + + public stop() {} +} diff --git a/plugins/wazuh-endpoints/server/types.ts b/plugins/wazuh-endpoints/server/types.ts new file mode 100644 index 0000000000..773101157d --- /dev/null +++ b/plugins/wazuh-endpoints/server/types.ts @@ -0,0 +1,10 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AppPluginStartDependencies {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface WazuhEndpointsPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface WazuhEndpointsPluginStart {} + +export type PluginSetup = {}; + +export interface AppPluginStartDependencies {} diff --git a/plugins/wazuh-endpoints/test/jest/config.js b/plugins/wazuh-endpoints/test/jest/config.js new file mode 100644 index 0000000000..c49cd92aa0 --- /dev/null +++ b/plugins/wazuh-endpoints/test/jest/config.js @@ -0,0 +1,41 @@ +import path from 'path'; + +const kbnDir = path.resolve(__dirname, '../../../../'); + +export default { + rootDir: path.resolve(__dirname, '../..'), + roots: ['/public', '/server', '/common'], + modulePaths: [`${kbnDir}/node_modules`], + collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', './!**/node_modules/**'], + moduleNameMapper: { + '^ui/(.*)': `${kbnDir}/src/ui/public/$1`, + // eslint-disable-next-line max-len + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `${kbnDir}/src/dev/jest/mocks/file_mock.js`, + '\\.(css|less|scss)$': `${kbnDir}/src/dev/jest/mocks/style_mock.js`, + axios: 'axios/dist/node/axios.cjs', + }, + setupFiles: [ + `${kbnDir}/src/dev/jest/setup/babel_polyfill.js`, + `${kbnDir}/src/dev/jest/setup/enzyme.js`, + ], + collectCoverage: true, + coverageDirectory: './target/test-coverage', + coverageReporters: ['html', 'text-summary', 'json-summary'], + globals: { + 'ts-jest': { + skipBabel: true, + }, + }, + moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'html'], + modulePathIgnorePatterns: ['__fixtures__/', 'target/'], + testMatch: ['**/*.test.{js,ts,tsx}'], + transform: { + '^.+\\.js$': `${kbnDir}/src/dev/jest/babel_transform.js`, + '^.+\\.tsx?$': `${kbnDir}/src/dev/jest/babel_transform.js`, + '^.+\\.html?$': `${kbnDir}/src/dev/jest/babel_transform.js`, + }, + transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.js$'], + snapshotSerializers: [`${kbnDir}/node_modules/enzyme-to-json/serializer`], + testEnvironment: 'jest-environment-jsdom', + reporters: ['default', `${kbnDir}/src/dev/jest/junit_reporter.js`], +}; diff --git a/plugins/wazuh-endpoints/translations/en-US.json b/plugins/wazuh-endpoints/translations/en-US.json new file mode 100644 index 0000000000..9022cc65e3 --- /dev/null +++ b/plugins/wazuh-endpoints/translations/en-US.json @@ -0,0 +1,79 @@ +{ + "formats": { + "number": { + "currency": { + "style": "currency" + }, + "percent": { + "style": "percent" + } + }, + "date": { + "short": { + "month": "numeric", + "day": "numeric", + "year": "2-digit" + }, + "medium": { + "month": "short", + "day": "numeric", + "year": "numeric" + }, + "long": { + "month": "long", + "day": "numeric", + "year": "numeric" + }, + "full": { + "weekday": "long", + "month": "long", + "day": "numeric", + "year": "numeric" + } + }, + "time": { + "short": { + "hour": "numeric", + "minute": "numeric" + }, + "medium": { + "hour": "numeric", + "minute": "numeric", + "second": "numeric" + }, + "long": { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short" + }, + "full": { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short" + } + }, + "relative": { + "years": { + "units": "year" + }, + "months": { + "units": "month" + }, + "days": { + "units": "day" + }, + "hours": { + "units": "hour" + }, + "minutes": { + "units": "minute" + }, + "seconds": { + "units": "second" + } + } + }, + "messages": {} +} diff --git a/plugins/wazuh-endpoints/tsconfig.json b/plugins/wazuh-endpoints/tsconfig.json new file mode 100644 index 0000000000..d3b63f9aee --- /dev/null +++ b/plugins/wazuh-endpoints/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*", + "public/hooks" + ], + "exclude": [] +} \ No newline at end of file From 72289d5e18e85790b806f6e143f476e0301ca26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chantal=20Bel=C3=A9n=20kelm?= <99441266+chantal-kelm@users.noreply.github.com> Date: Thu, 21 Dec 2023 06:37:14 -0300 Subject: [PATCH 012/136] Develop logic of a new index for the fim module (#6227) * Create index pattern to be used in file integrity monitoring module and create checks on the health-check of the index pattern * adjustments to the health check for the fim index pattern * tab dashboard name change * merge with master * delete console.log * update changelog * correction of unit test of fim * correction of unit test of fim * correction of unit test of fim * correction of unit test of fim --- CHANGELOG.md | 1 + plugins/main/common/config-equivalences.js | 2 + plugins/main/common/constants.ts | 30 + .../common/modules/modules-defaults.js | 17 +- .../health-check.container.test.tsx.snap | 17 + .../container/health-check.container.test.tsx | 1 + .../container/health-check.container.tsx | 14 + .../overview/fim/dashboard/dashboard.tsx | 108 +++ .../fim/dashboard/dashboard_panels.ts | 682 ++++++++++++++++++ .../fim/dashboard/dashboard_panels_filters.ts | 160 ++++ .../fim/dashboard/dashboard_panels_kpis.ts | 416 +++++++++++ .../overview/fim/dashboard/fim_filters.scss | 6 + .../overview/fim/dashboard/index.tsx | 1 + .../overview/fim/inventory/config/index.ts | 30 + .../overview/fim/inventory/index.tsx | 1 + .../overview/fim/inventory/inventory.scss | 13 + .../overview/fim/inventory/inventory.tsx | 244 +++++++ .../fim/inventory/inventory_service.ts | 224 ++++++ .../redux/reducers/appConfigReducers.ts | 31 +- .../fixtures/configuration.panel.text.json | 5 + plugins/wazuh-core/common/constants.ts | 27 + 21 files changed, 2014 insertions(+), 16 deletions(-) create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard.tsx create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts create mode 100644 plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts create mode 100644 plugins/main/public/components/overview/fim/dashboard/fim_filters.scss create mode 100644 plugins/main/public/components/overview/fim/dashboard/index.tsx create mode 100644 plugins/main/public/components/overview/fim/inventory/config/index.ts create mode 100644 plugins/main/public/components/overview/fim/inventory/index.tsx create mode 100644 plugins/main/public/components/overview/fim/inventory/inventory.scss create mode 100644 plugins/main/public/components/overview/fim/inventory/inventory.tsx create mode 100644 plugins/main/public/components/overview/fim/inventory/inventory_service.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 02971f0fce..7f44ba8f6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 4.9.0 - Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145) - Remove embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) +- Develop logic of a new index for the fim module [#6227](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6227) ## Wazuh v4.8.1 - OpenSearch Dashboards 2.10.0 - Revision 00 diff --git a/plugins/main/common/config-equivalences.js b/plugins/main/common/config-equivalences.js index fcfd359454..83aebea711 100644 --- a/plugins/main/common/config-equivalences.js +++ b/plugins/main/common/config-equivalences.js @@ -98,6 +98,8 @@ export const nameEquivalence = { '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'; diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index 4949a30685..46831badb9 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -51,6 +51,9 @@ export const WAZUH_STATISTICS_DEFAULT_CRON_FREQ = '0 */5 * * * *'; // Wazuh vulnerabilities export const WAZUH_VULNERABILITIES_PATTERN = 'wazuh-states-vulnerabilities'; +// Wazuh fim +export const WAZUH_FIM_PATTERN = 'wazuh-states-fim'; + // Job - Wazuh initialize export const WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME = 'wazuh-kibana'; @@ -861,6 +864,33 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { 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.', diff --git a/plugins/main/public/components/common/modules/modules-defaults.js b/plugins/main/public/components/common/modules/modules-defaults.js index 24b3b2b421..8c3fd06459 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.js +++ b/plugins/main/public/components/common/modules/modules-defaults.js @@ -11,9 +11,7 @@ */ import { Dashboard } from './dashboard'; import { Events } from './events'; -import { MainFim } from '../../agents/fim'; import { MainSca } from '../../agents/sca'; -import { MainVuls } from '../../agents/vuls'; import { MainMitre } from './main-mitre'; import { ModuleMitreAttackIntelligence } from '../../overview/mitre_attack_intelligence'; import { ComplianceTable } from '../../overview/compliance-table'; @@ -21,8 +19,10 @@ import ButtonModuleExploreAgent from '../../../controllers/overview/components/o import { ButtonModuleGenerateReport } from '../modules/buttons'; import { OfficePanel } from '../../overview/office-panel'; import { GitHubPanel } from '../../overview/github-panel'; -import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities' -import { withModuleNotForAgent, withModuleTabLoader } from '../hocs'; +import { DashboardVuls, InventoryVuls } from '../../overview/vulnerabilities'; +import { withModuleNotForAgent } from '../hocs'; +import { DashboardFim } from '../../overview/fim/dashboard/dashboard'; +import { InventoryFim } from '../../overview/fim/inventory/inventory'; const DashboardTab = { id: 'dashboard', @@ -56,12 +56,17 @@ export const ModulesDefaults = { fim: { init: 'dashboard', tabs: [ - DashboardTab, + { + id: 'dashboard', + name: 'Dashboard', + buttons: [ButtonModuleExploreAgent], + component: DashboardFim, + }, { id: 'inventory', name: 'Inventory', buttons: [ButtonModuleExploreAgent], - component: MainFim, + component: InventoryFim, }, EventsTab, ], diff --git a/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap b/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap index 9b6a456c8f..9e6a4d67a0 100644 --- a/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap +++ b/plugins/main/public/components/health-check/container/__snapshots__/health-check.container.test.tsx.snap @@ -122,6 +122,23 @@ exports[`Health Check container should render a Health check screen 1`] = ` title="Check vulnerabilities index pattern" validationService={[Function]} /> +
({ 'checks.template': true, 'checks.fields': true, 'checks.vulnerabilities.pattern': true, + 'checks.fim.pattern': true, }, }), useRootScope: () => ({}), diff --git a/plugins/main/public/components/health-check/container/health-check.container.tsx b/plugins/main/public/components/health-check/container/health-check.container.tsx index 29c1252545..f3be74bc46 100644 --- a/plugins/main/public/components/health-check/container/health-check.container.tsx +++ b/plugins/main/public/components/health-check/container/health-check.container.tsx @@ -37,6 +37,7 @@ import { WAZUH_INDEX_TYPE_MONITORING, WAZUH_INDEX_TYPE_STATISTICS, WAZUH_INDEX_TYPE_VULNERABILITIES, + WAZUH_INDEX_TYPE_FIM, } from '../../../../common/constants'; import { compose } from 'redux'; @@ -103,6 +104,19 @@ const checks = { shouldCheck: false, canRetry: true, }, + 'fim.pattern': { + title: 'Check fim index pattern', + label: 'Fim index pattern', + validator: appConfig => + checkPatternSupportService( + appConfig.data['fim.pattern'], + WAZUH_INDEX_TYPE_FIM, + NOT_TIME_FIELD_NAME_INDEX_PATTERN, + ), + awaitFor: [], + shouldCheck: false, + canRetry: true, + }, }; function HealthCheckComponent() { diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx b/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx new file mode 100644 index 0000000000..b13fd11b91 --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/dashboard.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { getPlugins } from '../../../../kibana-services'; +import { ViewMode } from '../../../../../../../src/plugins/embeddable/public'; +import { getDashboardPanels } from './dashboard_panels'; +import { I18nProvider } from '@osd/i18n/react'; +import useSearchBar from '../../../common/search-bar/use-search-bar'; +import { getDashboardFilters } from './dashboard_panels_filters'; +import './fim_filters.scss'; +import { getKPIsPanel } from './dashboard_panels_kpis'; +import { useAppConfig } from '../../../common/hooks'; + +const plugins = getPlugins(); + +const SearchBar = getPlugins().data.ui.SearchBar; + +const DashboardByRenderer = plugins.dashboard.DashboardContainerByValueRenderer; + +export const DashboardFim: React.FC = () => { + const appConfig = useAppConfig(); + const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern']; + + const { searchBarProps } = useSearchBar({ + defaultIndexPatternID: FIM_INDEX_PATTERN_ID, + filters: [], + }); + + return ( + <> + + + +
+ +
+ + + + ); +}; diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts new file mode 100644 index 0000000000..044622307e --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels.ts @@ -0,0 +1,682 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateTopFim = (indexPatternId: string) => { + return { + id: 'most_detected_fim', + title: 'Most detected fim', + type: 'horizontal_bar', + params: { + type: 'histogram', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'left', + show: true, + style: {}, + scale: { + type: 'linear', + }, + labels: { + show: true, + rotate: 0, + filter: false, + truncate: 200, + }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'bottom', + show: true, + style: {}, + scale: { + type: 'linear', + mode: 'normal', + }, + labels: { + show: true, + rotate: 75, + filter: true, + truncate: 100, + }, + title: { + text: 'Count', + }, + }, + ], + seriesParams: [ + { + show: true, + type: 'histogram', + mode: 'stacked', + data: { + label: 'Count', + id: '1', + }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true, + }, + ], + addTooltip: true, + addLegend: false, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.id', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'Vulnerability.ID', + }, + schema: 'segment', + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.id', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateTopFimEndpoints = (indexPatternId: string) => { + return { + id: 'most_vulnerable_endpoints_fim', + title: 'The most vulnerable endpoints', + type: 'horizontal_bar', + params: { + type: 'histogram', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'left', + show: true, + style: {}, + scale: { + type: 'linear', + }, + labels: { + show: true, + rotate: 0, + filter: false, + truncate: 200, + }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'bottom', + show: true, + style: {}, + scale: { + type: 'linear', + mode: 'normal', + }, + labels: { + show: true, + rotate: 75, + filter: true, + truncate: 100, + }, + title: { + text: 'Count', + }, + }, + ], + seriesParams: [ + { + show: true, + type: 'histogram', + mode: 'stacked', + data: { + label: 'Count', + id: '1', + }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true, + }, + ], + addTooltip: true, + addLegend: false, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + }, + uiState: { + vis: { + legendOpen: false, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: 'Count', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: 'agent.id', + orderBy: '1', + order: 'desc', + size: 10, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'package.path', + }, + schema: 'segment', + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'agent.id', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'mm', + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateAccumulationMostDetectedFim = (indexPatternId: string) => { + return { + id: 'accumulation_most_vulnerable_fim', + title: 'Accumulation of the most detected fim', + type: 'line', + params: { + type: 'line', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'bottom', + show: true, + style: {}, + scale: { + type: 'linear', + }, + labels: { + show: true, + filter: true, + truncate: 100, + }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'left', + show: true, + style: {}, + scale: { + type: 'linear', + mode: 'normal', + }, + labels: { + show: true, + rotate: 0, + filter: false, + truncate: 100, + }, + title: { + text: 'Count', + }, + }, + ], + seriesParams: [ + { + show: true, + type: 'line', + mode: 'normal', + data: { + label: 'Count', + id: '1', + }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: false, + lineWidth: 2, + interpolate: 'linear', + showCircles: true, + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + radiusRatio: 20, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'count', + params: {}, + schema: 'radius', + }, + { + id: '4', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.id', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Others', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'group', + }, + { + id: '3', + enabled: true, + type: 'date_histogram', + params: { + field: 'event.created', + timeRange: { + from: 'now-24h', + to: 'now', + }, + useNormalizedOpenSearchInterval: true, + scaleMetricValues: false, + interval: 'w', + // eslint-disable-next-line camelcase + drop_partials: false, + // eslint-disable-next-line camelcase + min_doc_count: 1, + // eslint-disable-next-line camelcase + extended_bounds: {}, + }, + schema: 'segment', + }, + ], + }, + }; +}; + +const getVisStateInventoryTable = (indexPatternId: string) => { + return { + id: 'inventory_table_fim', + title: 'Inventory table', + type: 'table', + params: { + perPage: 5, + showPartialRows: false, + showMetricsAtAllLevels: false, + showTotal: false, + totalFunc: 'sum', + percentageCol: '', + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: 'Count', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: 'package.name', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'name', + }, + schema: 'bucket', + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'package.version', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'version', + }, + schema: 'bucket', + }, + { + id: '4', + enabled: true, + type: 'terms', + params: { + field: 'package.architecture', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'architecture', + }, + schema: 'bucket', + }, + { + id: '5', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.severity', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'severity', + }, + schema: 'bucket', + }, + { + id: '6', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.id', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'id', + }, + schema: 'bucket', + }, + { + id: '7', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.score.version', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'score version', + }, + schema: 'bucket', + }, + { + id: '8', + enabled: true, + type: 'terms', + params: { + field: 'vulnerability.score.base', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: 'score base', + }, + schema: 'bucket', + }, + ], + }, + }; +}; + +export const getDashboardPanels = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + '6': { + gridData: { + w: 16, + h: 12, + x: 0, + y: 0, + i: '6', + }, + type: 'visualization', + explicitInput: { + id: '6', + savedVis: getVisStateTopFim(indexPatternId), + }, + }, + '7': { + gridData: { + w: 16, + h: 12, + x: 16, + y: 0, + i: '7', + }, + type: 'visualization', + explicitInput: { + id: '7', + savedVis: getVisStateTopFimEndpoints(indexPatternId), + }, + }, + '8': { + gridData: { + w: 16, + h: 12, + x: 32, + y: 0, + i: '8', + }, + type: 'visualization', + explicitInput: { + id: '8', + savedVis: getVisStateAccumulationMostDetectedFim(indexPatternId), + }, + }, + '9': { + gridData: { + w: 48, + h: 12, + x: 0, + y: 14, + i: '9', + }, + type: 'visualization', + explicitInput: { + id: '9', + savedVis: getVisStateInventoryTable(indexPatternId), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts new file mode 100644 index 0000000000..47a6d63ebb --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_filters.ts @@ -0,0 +1,160 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateFilter = ( + id: string, + indexPatternId: string, + title: string, + label: string, + fieldName: string, +) => { + return { + id, + title, + type: 'table', + params: { + perPage: 5, + percentageCol: '', + row: true, + showMetricsAtAllLevels: false, + showPartialRows: false, + showTotal: false, + totalFunc: 'sum', + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: 'Count', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: fieldName, + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + customLabel: label, + }, + schema: 'bucket', + }, + ], + }, + }; +}; + +export const getDashboardFilters = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + topPackageSelector: { + gridData: { + w: 12, + h: 12, + x: 0, + y: 0, + i: 'topPackageSelector', + }, + type: 'visualization', + explicitInput: { + id: 'topPackageSelector', + savedVis: getVisStateFilter( + 'topPackageSelector', + indexPatternId, + 'Top Packages fim', + 'Package', + 'package.name', + ), + }, + }, + topOSFim: { + gridData: { + w: 12, + h: 12, + x: 12, + y: 0, + i: 'topOSFim', + }, + type: 'visualization', + explicitInput: { + id: 'topOSFim', + savedVis: getVisStateFilter( + 'topOSFim', + indexPatternId, + 'Top Operating system fim', + 'Operating system', + 'host.os.name', + ), + }, + }, + topAgentFim: { + gridData: { + w: 12, + h: 12, + x: 24, + y: 0, + i: 'topAgentFim', + }, + type: 'visualization', + explicitInput: { + id: 'topAgentFim', + savedVis: getVisStateFilter( + 'topAgentFim', + indexPatternId, + 'Agent filter', + 'Agent', + 'agent.id', + ), + }, + }, + topFim: { + gridData: { + w: 12, + h: 12, + x: 36, + y: 0, + i: 'topFim', + }, + type: 'visualization', + explicitInput: { + id: 'topFim', + savedVis: getVisStateFilter( + 'topFim', + indexPatternId, + 'Top vulnerabilities', + 'Fim', + 'fim.id', + ), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts new file mode 100644 index 0000000000..99afc7f18f --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/dashboard_panels_kpis.ts @@ -0,0 +1,416 @@ +import { DashboardPanelState } from '../../../../../../../../src/plugins/dashboard/public/application'; +import { EmbeddableInput } from '../../../../../../../../src/plugins/embeddable/public'; + +const getVisStateSeverityCritical = (indexPatternId: string) => { + return { + id: 'severity_critical_fim', + title: 'Critical', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Reds', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: false, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 50, + }, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: ' ', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'vulnerability.severity:"critical"', + language: 'kuery', + }, + label: '- Critical Severity Alerts', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateSeverityHigh = (indexPatternId: string) => { + return { + id: 'severity_high_fim', + title: 'High', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Blues', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: false, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 50, + }, + }, + }, + uiState: { + vis: { + colors: { + 'High Severity Alerts - Count': '#38D1BA', + }, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: ' ', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'vulnerability.severity:"high"', + language: 'kuery', + }, + label: '- High Severity Alerts', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateSeverityMedium = (indexPatternId: string) => { + return { + id: 'severity_medium_fim', + title: 'Medium', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Yellow to Red', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: true, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 50, + }, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: ' ', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'vulnerability.severity:"medium"', + language: 'kuery', + }, + label: '- Medium Severity Alerts', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +const getVisStateSeverityLow = (indexPatternId: string) => { + return { + id: 'severity_low_fim', + title: 'Low', + type: 'metric', + params: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: 'Greens', + metricColorMode: 'Labels', + colorsRange: [ + { + from: 0, + to: 0, + }, + { + from: 0, + to: 0, + }, + ], + labels: { + show: true, + }, + invertColors: false, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 50, + }, + }, + }, + data: { + searchSource: { + query: { + language: 'kuery', + query: '', + }, + filter: [], + index: indexPatternId, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: indexPatternId, + }, + ], + aggs: [ + { + id: '1', + enabled: true, + type: 'count', + params: { + customLabel: ' ', + }, + schema: 'metric', + }, + { + id: '2', + enabled: true, + type: 'filters', + params: { + filters: [ + { + input: { + query: 'vulnerability.severity:"low"', + language: 'kuery', + }, + label: '- Low Severity Alerts', + }, + ], + }, + schema: 'group', + }, + ], + }, + }; +}; + +export const getKPIsPanel = ( + indexPatternId: string, +): { + [panelId: string]: DashboardPanelState< + EmbeddableInput & { [k: string]: unknown } + >; +} => { + return { + '1': { + gridData: { + w: 12, + h: 6, + x: 0, + y: 0, + i: '1', + }, + type: 'visualization', + explicitInput: { + id: '1', + savedVis: getVisStateSeverityCritical(indexPatternId), + }, + }, + '2': { + gridData: { + w: 12, + h: 6, + x: 12, + y: 0, + i: '2', + }, + type: 'visualization', + explicitInput: { + id: '2', + savedVis: getVisStateSeverityHigh(indexPatternId), + }, + }, + '3': { + gridData: { + w: 12, + h: 6, + x: 24, + y: 0, + i: '3', + }, + type: 'visualization', + explicitInput: { + id: '3', + savedVis: getVisStateSeverityMedium(indexPatternId), + }, + }, + '4': { + gridData: { + w: 12, + h: 6, + x: 36, + y: 0, + i: '4', + }, + type: 'visualization', + explicitInput: { + id: '4', + savedVis: getVisStateSeverityLow(indexPatternId), + }, + }, + }; +}; diff --git a/plugins/main/public/components/overview/fim/dashboard/fim_filters.scss b/plugins/main/public/components/overview/fim/dashboard/fim_filters.scss new file mode 100644 index 0000000000..a836b86e3e --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/fim_filters.scss @@ -0,0 +1,6 @@ +.fim-dashboard-filters-wrapper { + .euiDataGrid__controls, + .euiDataGrid__pagination { + display: none !important; + } +} diff --git a/plugins/main/public/components/overview/fim/dashboard/index.tsx b/plugins/main/public/components/overview/fim/dashboard/index.tsx new file mode 100644 index 0000000000..b691822976 --- /dev/null +++ b/plugins/main/public/components/overview/fim/dashboard/index.tsx @@ -0,0 +1 @@ +export * from './dashboard'; \ No newline at end of file diff --git a/plugins/main/public/components/overview/fim/inventory/config/index.ts b/plugins/main/public/components/overview/fim/inventory/config/index.ts new file mode 100644 index 0000000000..aa5490ffd8 --- /dev/null +++ b/plugins/main/public/components/overview/fim/inventory/config/index.ts @@ -0,0 +1,30 @@ +import { EuiDataGridColumn } from '@elastic/eui'; + +export const MAX_ENTRIES_PER_QUERY = 10000; + +export const inventoryTableDefaultColumns: EuiDataGridColumn[] = [ + { + id: 'package.name', + }, + { + id: 'package.version', + }, + { + id: 'package.architecture', + }, + { + id: 'fim.severity', + }, + { + id: 'fim.id', + }, + { + id: 'fim.score.version', + }, + { + id: 'fim.score.base', + }, + { + id: 'event.created', + }, +]; diff --git a/plugins/main/public/components/overview/fim/inventory/index.tsx b/plugins/main/public/components/overview/fim/inventory/index.tsx new file mode 100644 index 0000000000..ddb0742f5e --- /dev/null +++ b/plugins/main/public/components/overview/fim/inventory/index.tsx @@ -0,0 +1 @@ +export * from './inventory'; \ No newline at end of file diff --git a/plugins/main/public/components/overview/fim/inventory/inventory.scss b/plugins/main/public/components/overview/fim/inventory/inventory.scss new file mode 100644 index 0000000000..2303093ed2 --- /dev/null +++ b/plugins/main/public/components/overview/fim/inventory/inventory.scss @@ -0,0 +1,13 @@ +.fimInventoryContainer { + height: calc(100vh - 104px); +} + +.headerIsExpanded .fimInventoryContainer { + height: calc(100vh - 153px); +} + +.fimInventoryContainer .euiDataGrid--fullScreen { + height: calc(100vh - 49px); + bottom: 0; + top: auto; +} diff --git a/plugins/main/public/components/overview/fim/inventory/inventory.tsx b/plugins/main/public/components/overview/fim/inventory/inventory.tsx new file mode 100644 index 0000000000..39503b0e94 --- /dev/null +++ b/plugins/main/public/components/overview/fim/inventory/inventory.tsx @@ -0,0 +1,244 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { getPlugins } from '../../../../kibana-services'; +import useSearchBar from '../../../common/search-bar/use-search-bar'; +import { IntlProvider } from 'react-intl'; +import { + EuiDataGrid, + EuiPageTemplate, + EuiToolTip, + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiButtonEmpty, +} from '@elastic/eui'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { SearchResponse } from '../../../../../../../../src/core/server'; +import DocViewer from '../../vulnerabilities/doc_viewer/doc_viewer'; +import { DiscoverNoResults } from '../../vulnerabilities/common/components/no_results'; +import { LoadingSpinner } from '../../vulnerabilities/common/components/loading_spinner'; +import { useDataGrid } from '../../vulnerabilities/data_grid/use_data_grid'; +import { + MAX_ENTRIES_PER_QUERY, + inventoryTableDefaultColumns, +} from '../../vulnerabilities/dashboards/inventory/config'; +import { useDocViewer } from '../../vulnerabilities/doc_viewer/use_doc_viewer'; +import './inventory.scss'; +import { + search, + exportSearchToCSV, +} from '../../vulnerabilities/dashboards/inventory/inventory_service'; +import { + ErrorHandler, + ErrorFactory, + HttpError, +} from '../../../../react-services/error-management'; +import { withErrorBoundary } from '../../../common/hocs'; +import { HitsCounter } from '../../../../kibana-integrations/discover/application/components/hits_counter/hits_counter'; +import { formatNumWithCommas } from '../../../../kibana-integrations/discover/application/helpers'; +import { useAppConfig } from '../../../common/hooks'; + +const InventoryFimComponent = () => { + const appConfig = useAppConfig(); + const FIM_INDEX_PATTERN_ID = appConfig.data['fim.pattern']; + const { searchBarProps } = useSearchBar({ + defaultIndexPatternID: FIM_INDEX_PATTERN_ID, + }); + const { isLoading, filters, query, indexPatterns } = searchBarProps; + const SearchBar = getPlugins().data.ui.SearchBar; + const [results, setResults] = useState({} as SearchResponse); + const [inspectedHit, setInspectedHit] = useState(undefined); + const [indexPattern, setIndexPattern] = useState( + undefined, + ); + const [isSearching, setIsSearching] = useState(false); + const [isExporting, setIsExporting] = useState(false); + + const onClickInspectDoc = useMemo( + () => (index: number) => { + const rowClicked = results.hits.hits[index]; + setInspectedHit(rowClicked); + }, + [results], + ); + + const DocViewInspectButton = ({ + rowIndex, + }: EuiDataGridCellValueElementProps) => { + const inspectHintMsg = 'Inspect document details'; + return ( + + onClickInspectDoc(rowIndex)} + iconType='inspect' + aria-label={inspectHintMsg} + /> + + ); + }; + + const dataGridProps = useDataGrid({ + ariaLabelledBy: 'Fim Inventory Table', + defaultColumns: inventoryTableDefaultColumns, + results, + indexPattern: indexPattern as IndexPattern, + DocViewInspectButton, + }); + + const { pagination, sorting, columnVisibility } = dataGridProps; + + const docViewerProps = useDocViewer({ + doc: inspectedHit, + indexPattern: indexPattern as IndexPattern, + }); + + useEffect(() => { + if (!isLoading) { + setIndexPattern(indexPatterns?.[0] as IndexPattern); + search({ + indexPattern: indexPatterns?.[0] as IndexPattern, + filters, + query, + pagination, + sorting, + }) + .then(results => { + setResults(results); + setIsSearching(false); + }) + .catch(error => { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error fetching fim', + }); + ErrorHandler.handleError(searchError); + setIsSearching(false); + }); + } + }, [ + JSON.stringify(searchBarProps), + JSON.stringify(pagination), + JSON.stringify(sorting), + ]); + + const timeField = indexPattern?.timeFieldName + ? indexPattern.timeFieldName + : undefined; + + const onClickExportResults = async () => { + const params = { + indexPattern: indexPatterns?.[0] as IndexPattern, + filters, + query, + fields: columnVisibility.visibleColumns, + pagination: { + pageIndex: 0, + pageSize: results.hits.total, + }, + sorting, + }; + try { + setIsExporting(true); + await exportSearchToCSV(params); + } catch (error) { + const searchError = ErrorFactory.create(HttpError, { + error, + message: 'Error downloading csv report', + }); + ErrorHandler.handleError(searchError); + } finally { + setIsExporting(false); + } + }; + + return ( + + + <> + {isLoading ? ( + + ) : ( + + )} + {isSearching ? : null} + {!isLoading && !isSearching && results?.hits?.total === 0 ? ( + + ) : null} + {!isLoading && !isSearching && results?.hits?.total > 0 ? ( + + {}} + tooltip={ + results?.hits?.total && + results?.hits?.total > MAX_ENTRIES_PER_QUERY + ? { + ariaLabel: 'Warning', + content: `The query results has exceeded the limit of 10,000 hits. To provide a better experience the table only shows the first ${formatNumWithCommas( + MAX_ENTRIES_PER_QUERY, + )} hits.`, + iconType: 'alert', + position: 'top', + } + : undefined + } + /> + + Export Formated + + + ), + }} + /> + ) : null} + {inspectedHit && ( + setInspectedHit(undefined)} size='m'> + + +

Document Details

+
+
+ + + + + + + +
+ )} + +
+
+ ); +}; + +export const InventoryFim = withErrorBoundary(InventoryFimComponent); diff --git a/plugins/main/public/components/overview/fim/inventory/inventory_service.ts b/plugins/main/public/components/overview/fim/inventory/inventory_service.ts new file mode 100644 index 0000000000..82ef36b007 --- /dev/null +++ b/plugins/main/public/components/overview/fim/inventory/inventory_service.ts @@ -0,0 +1,224 @@ +import { SearchResponse } from '../../../../../../../../src/core/server'; +import { getPlugins } from '../../../../kibana-services'; +import { + IndexPattern, + Filter, + OpenSearchQuerySortValue, +} from '../../../../../../../../src/plugins/data/public'; +import * as FileSaver from '../../../../../services/file-saver'; +import { beautifyDate } from '../../../../agents/vuls/inventory/lib'; +import { MAX_ENTRIES_PER_QUERY } from './config'; + +interface SearchParams { + indexPattern: IndexPattern; + filters?: Filter[]; + query?: any; + pagination?: { + pageIndex?: number; + pageSize?: number; + }; + fields?: string[]; + sorting?: { + columns: { + id: string; + direction: 'asc' | 'desc'; + }[]; + }; +} + +export const search = async ( + params: SearchParams, +): Promise => { + const { + indexPattern, + filters = [], + query, + pagination, + sorting, + fields, + } = params; + if (!indexPattern) { + return; + } + const data = getPlugins().data; + const searchSource = await data.search.searchSource.create(); + const fromField = + (pagination?.pageIndex || 0) * (pagination?.pageSize || 100); + const sortOrder: OpenSearchQuerySortValue[] = + sorting?.columns.map(column => { + const sortDirection = column.direction === 'asc' ? 'asc' : 'desc'; + return { [column?.id || '']: sortDirection } as OpenSearchQuerySortValue; + }) || []; + + const searchParams = searchSource + .setParent(undefined) + .setField('filter', filters) + .setField('query', query) + .setField('sort', sortOrder) + .setField('size', pagination?.pageSize) + .setField('from', fromField) + .setField('index', indexPattern); + + // add fields + if (fields && Array.isArray(fields) && fields.length > 0) { + searchParams.setField('fields', fields); + } + try { + return await searchParams.fetch(); + } catch (error) { + if (error.body) { + throw error.body; + } + throw error; + } +}; + +export const parseData = ( + resultsHits: SearchResponse['hits']['hits'], +): any[] => { + const data = resultsHits.map(hit => { + if (!hit) { + return {}; + } + const source = hit._source as object; + const data = { + ...source, + _id: hit._id, + _index: hit._index, + _type: hit._type, + _score: hit._score, + }; + return data; + }); + return data; +}; + +export const getFieldFormatted = ( + rowIndex, + columnId, + indexPattern, + rowsParsed, +) => { + const field = indexPattern.fields.find(field => field.name === columnId); + let fieldValue = null; + if (columnId.includes('.')) { + // when the column is a nested field. The column could have 2 to n levels + // get dinamically the value of the nested field + const nestedFields = columnId.split('.'); + fieldValue = rowsParsed[rowIndex]; + nestedFields.forEach(field => { + if (fieldValue) { + fieldValue = fieldValue[field]; + } + }); + } else { + fieldValue = rowsParsed[rowIndex][columnId].formatted + ? rowsParsed[rowIndex][columnId].formatted + : rowsParsed[rowIndex][columnId]; + } + + // if is date field + if (field?.type === 'date') { + // @ts-ignore + fieldValue = beautifyDate(fieldValue); + } + return fieldValue; +}; + +export const exportSearchToCSV = async ( + params: SearchParams, +): Promise => { + const DEFAULT_MAX_SIZE_PER_CALL = 1000; + const { + indexPattern, + filters = [], + query, + sorting, + fields, + pagination, + } = params; + // when the pageSize is greater than the default max size per call (10000) + // then we need to paginate the search + const mustPaginateSearch = + pagination?.pageSize && pagination?.pageSize > DEFAULT_MAX_SIZE_PER_CALL; + const pageSize = mustPaginateSearch + ? DEFAULT_MAX_SIZE_PER_CALL + : pagination?.pageSize; + const totalHits = pagination?.pageSize || DEFAULT_MAX_SIZE_PER_CALL; + let pageIndex = params.pagination?.pageIndex || 0; + let hitsCount = 0; + let allHits = []; + let searchResults; + if (mustPaginateSearch) { + // paginate the search + while (hitsCount < totalHits && hitsCount < MAX_ENTRIES_PER_QUERY) { + const searchParams = { + indexPattern, + filters, + query, + pagination: { + pageIndex, + pageSize, + }, + sorting, + fields, + }; + searchResults = await search(searchParams); + allHits = allHits.concat(searchResults.hits.hits); + hitsCount = allHits.length; + pageIndex++; + } + } else { + searchResults = await search(params); + allHits = searchResults.hits.hits; + } + + const resultsFields = fields; + const data = allHits.map(hit => { + // check if the field type is a date + const dateFields = indexPattern.fields.getByType('date'); + const dateFieldsNames = dateFields.map(field => field.name); + const flattenHit = indexPattern.flattenHit(hit); + // replace the date fields with the formatted date + dateFieldsNames.forEach(field => { + if (flattenHit[field]) { + flattenHit[field] = beautifyDate(flattenHit[field]); + } + }); + return flattenHit; + }); + + if (!resultsFields || resultsFields.length === 0) { + return; + } + + if (!data || data.length === 0) return; + + const parsedData = data + .map(row => { + const parsedRow = resultsFields?.map(field => { + const value = row[field]; + if (value === undefined || value === null) { + return ''; + } + if (typeof value === 'object') { + return JSON.stringify(value); + } + return `"${value}"`; + }); + return parsedRow?.join(','); + }) + .join('\n'); + + // create a csv file using blob + const blobData = new Blob([`${resultsFields?.join(',')}\n${parsedData}`], { + type: 'text/csv', + }); + + if (blobData) { + FileSaver?.saveAs( + blobData, + `fim_inventory-${new Date().toISOString()}.csv`, + ); + } +}; diff --git a/plugins/main/public/redux/reducers/appConfigReducers.ts b/plugins/main/public/redux/reducers/appConfigReducers.ts index 5f3d43fd92..a987ce70e9 100644 --- a/plugins/main/public/redux/reducers/appConfigReducers.ts +++ b/plugins/main/public/redux/reducers/appConfigReducers.ts @@ -18,12 +18,15 @@ const initialState: AppConfigState = { isLoading: false, isReady: false, hasError: false, - data: getSettingsDefault(), + data: { + 'vulnerabilities.pattern': 'wazuh-states-vulnerabilities', + 'fim.pattern': 'wazuh-states-fim', + }, }; const appConfigReducer: Reducer = ( state = initialState, - action + action, ) => { switch (action.type) { case 'UPDATE_APP_CONFIG_SET_IS_LOADING': @@ -31,22 +34,30 @@ const appConfigReducer: Reducer = ( ...state, isLoading: true, isReady: false, - hasError: false + hasError: false, }; case 'UPDATE_APP_CONFIG_SET_HAS_ERROR': - return { - ...state, - isLoading: false, - isReady: false, - hasError: true - }; + return { + ...state, + isLoading: false, + isReady: false, + hasError: true, + }; case 'UPDATE_APP_CONFIG_DATA': return { ...state, isLoading: false, isReady: true, hasError: false, - data: {...state.data, ...action.payload}, + data: { ...state.data, ...action.payload }, + }; + case 'UPDATE_FIM_PATTERN': + return { + ...state, + data: { + ...state.data, + 'fim.pattern': action.payload, + }, }; default: return state; diff --git a/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json b/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json index 5f23722d0f..5a4d7878de 100644 --- a/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json +++ b/plugins/main/test/cypress/cypress/fixtures/configuration.panel.text.json @@ -99,6 +99,11 @@ "title": "Vulnerabilities index pattern", "subTitle": "Enable or disable the vulnerabilities index pattern health check when opening the app.", "label": "checks.vulnerabilities.pattern" + }, + { + "title": "Fim index pattern", + "subTitle": "Enable or disable the fim index pattern health check when opening the app.", + "label": "checks.fim.pattern" } ] }, diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts index 4949a30685..180f6679dc 100644 --- a/plugins/wazuh-core/common/constants.ts +++ b/plugins/wazuh-core/common/constants.ts @@ -861,6 +861,33 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { 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.', From ff561c53bc97fb17307c28adc2ae5d64040369ff Mon Sep 17 00:00:00 2001 From: Antonio <34042064+Desvelao@users.noreply.github.com> Date: Wed, 27 Dec 2023 10:46:54 +0100 Subject: [PATCH 013/136] Replace the plugin logging system by the provided by the platform (#6161) * feat(logging): remove plugin logger from start tasks of main plugin - Enhance the logging messages - Minor enhancements * feat(logging): minor fixes to logging messages in the main plugin * feat(logging): remove custom logger from endpoints of main plugin * fix(logging): remove parameter of addJobToQueue * feat(core): replace the loggin service and move services to core plugin - Replace the logging services - Move services to core plugin - CacheAPIUserAllowRunAs - ManageHosts - ServerAPIClient (aka api-interceptor) - ServerAPIHostEntries (aka ) - UpdateConfigurationFile - UpdateRegistry - Adapt the usage of services in the main and check updates plugin from core plugin - Remove plugin logger from main and core plugins - Remove API endpoint - GET /utils/logs/ui - Remove unused code - Adapt the test suites * feat(logging): removed constants related to log files path * fix(dependency): removed winston dependency of plugins and update the yarn.lock files * fix(logging): removed logs.level plugin setting * fix(logging): fixed monitoring and statistics backend tasks * fix(monitoring): manage API host entries * feat(core): add description to core services classes * feat(docs): add docs to core plugin * feat(docs): add description to core services * test: fix of check updates plugin * test: fix of check updates plugin * feat(logging): remove the App logs application - Remove GET /utils/logs endpoint - Remove related tests - Remove App logs application * Fix test --------- Co-authored-by: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> Co-authored-by: Ian Yenien Serrano --- plugins/main/common/config-equivalences.js | 12 - plugins/main/common/constants.ts | 61 --- plugins/main/common/plugin-settings.test.ts | 3 - .../common/wazu-menu/wz-menu-settings.cy.ts | 1 - plugins/main/opensearch_dashboards.json | 1 + plugins/main/package.json | 5 +- .../main/public/components/security/main.tsx | 11 +- .../components/settings/api/api-table.js | 195 +++++--- .../components/settings/settings-logs/logs.js | 135 ------ .../main/public/controllers/settings/index.js | 3 - .../public/controllers/settings/settings.js | 51 -- .../controllers/settings/settings.test.ts | 17 +- plugins/main/public/factories/wazuh-config.js | 7 - plugins/main/public/kibana-services.ts | 2 + plugins/main/public/plugin.ts | 7 +- .../public/react-services/wazuh-config.js | 14 +- .../public/templates/settings/settings.html | 43 +- plugins/main/public/types.ts | 12 +- plugins/main/public/utils/applications.ts | 17 - plugins/main/server/controllers/wazuh-api.ts | 158 +++--- .../main/server/controllers/wazuh-elastic.ts | 188 +++----- .../main/server/controllers/wazuh-hosts.ts | 108 ++--- ...eporting-security-endpoint-handler.test.ts | 341 +++++++------ ...ity-endpoint-parameters-validation.test.ts | 421 +++++++++------- .../server/controllers/wazuh-reporting.ts | 207 ++++---- .../wazuh-utils/ui-logs.controller.test.ts | 51 +- .../wazuh-utils/ui-logs.controller.ts | 66 +-- .../controllers/wazuh-utils/wazuh-utils.ts | 276 +++++++---- plugins/main/server/lib/api-interceptor.ts | 120 ----- plugins/main/server/lib/base-logger.ts | 257 ---------- .../server/lib/cache-api-user-has-run-as.ts | 96 ---- plugins/main/server/lib/logger.ts | 22 - plugins/main/server/lib/manage-hosts.ts | 387 --------------- plugins/main/server/lib/parse-cron.ts | 25 +- .../lib/reporting/extended-information.ts | 448 ++++++++++-------- plugins/main/server/lib/reporting/printer.ts | 340 +++++++------ .../factories/default-factory.ts | 15 - .../lib/security-factory/factories/index.ts | 2 - .../opensearch-dashboards-security-factory.ts | 30 -- .../main/server/lib/security-factory/index.ts | 1 - .../lib/security-factory/security-factory.ts | 21 - plugins/main/server/lib/update-registry.ts | 238 ---------- plugins/main/server/plugin.ts | 112 ++--- .../routes/wazuh-api-http-status.test.ts | 72 ++- .../server/routes/wazuh-reporting.test.ts | 26 +- .../main/server/routes/wazuh-utils/ui-logs.ts | 10 +- .../routes/wazuh-utils/wazuh-utils.test.ts | 45 +- .../server/routes/wazuh-utils/wazuh-utils.ts | 66 +-- .../server/start/cron-scheduler/apiRequest.ts | 67 +-- .../start/cron-scheduler/error-handler.ts | 20 +- .../start/cron-scheduler/save-document.ts | 134 ++++-- .../start/cron-scheduler/scheduler-handler.ts | 120 +++-- .../cron-scheduler/scheduler-job.test.ts | 217 +++++---- .../start/cron-scheduler/scheduler-job.ts | 114 +++-- .../server/start/initialize/index.test.ts | 4 - plugins/main/server/start/initialize/index.ts | 193 +++----- .../server/start/migration-tasks/index.ts | 9 +- .../reports_directory_name.test.ts | 278 ++++++++--- .../migration-tasks/reports_directory_name.ts | 76 +-- plugins/main/server/start/monitoring/index.ts | 438 ++++++++++------- plugins/main/server/start/queue/index.ts | 46 +- .../start/tryCatchForIndexPermissionError.ts | 45 +- plugins/main/server/types.ts | 5 +- .../settings/wazuh-logs/reload-logs.when.js | 10 - plugins/main/test/server/wazuh-api.js | 26 +- plugins/main/yarn.lock | 161 +------ plugins/wazuh-check-updates/package.json | 3 +- .../server/plugin-services.ts | 14 +- plugins/wazuh-check-updates/server/plugin.ts | 30 +- .../saved-object/get-saved-object.test.ts | 30 +- .../services/saved-object/get-saved-object.ts | 16 +- .../saved-object/set-saved-object.test.ts | 33 +- .../services/saved-object/set-saved-object.ts | 13 +- .../services/updates/get-updates.test.ts | 75 ++- .../server/services/updates/get-updates.ts | 34 +- .../get-user-preferences.test.ts | 25 +- .../user-preferences/get-user-preferences.ts | 14 +- .../update-user-preferences.test.ts | 24 +- .../update-user-preferences.ts | 21 +- plugins/wazuh-check-updates/server/types.ts | 2 +- plugins/wazuh-check-updates/yarn.lock | 203 -------- .../common/api-user-status-run-as.ts | 23 + plugins/wazuh-core/common/constants.ts | 61 --- plugins/wazuh-core/docs/README.md | 20 + plugins/wazuh-core/package.json | 3 +- plugins/wazuh-core/public/plugin.ts | 7 +- plugins/wazuh-core/public/types.ts | 8 +- .../wazuh-core/server/controllers/index.ts | 12 - plugins/wazuh-core/server/plugin.ts | 96 +++- .../wazuh-core/server/services/api-client.ts | 18 - .../server/services/api-interceptor.ts | 120 ----- .../wazuh-core/server/services/base-logger.ts | 245 ---------- .../services/cache-api-user-has-run-as.ts | 132 +++--- .../server/services/cookie.ts} | 17 +- .../server/services/get-configuration.ts | 60 ++- plugins/wazuh-core/server/services/index.ts | 13 +- plugins/wazuh-core/server/services/logger.ts | 11 - .../server/services/manage-hosts.ts | 75 ++- .../opensearch-dashboards-security-factory.ts | 17 +- .../server/services/security-factory/index.ts | 2 +- .../security-factory/security-factory.ts | 20 +- .../server/services/server-api-client.ts | 249 ++++++++++ .../server-api-host-entries.ts} | 31 +- .../services/update-configuration-file.ts} | 36 +- .../server/services/update-registry.ts | 62 ++- plugins/wazuh-core/server/types.ts | 57 ++- plugins/wazuh-core/yarn.lock | 203 -------- 107 files changed, 3606 insertions(+), 5238 deletions(-) delete mode 100644 plugins/main/public/components/settings/settings-logs/logs.js delete mode 100644 plugins/main/server/lib/api-interceptor.ts delete mode 100644 plugins/main/server/lib/base-logger.ts delete mode 100644 plugins/main/server/lib/cache-api-user-has-run-as.ts delete mode 100644 plugins/main/server/lib/logger.ts delete mode 100644 plugins/main/server/lib/manage-hosts.ts delete mode 100644 plugins/main/server/lib/security-factory/factories/default-factory.ts delete mode 100644 plugins/main/server/lib/security-factory/factories/index.ts delete mode 100644 plugins/main/server/lib/security-factory/factories/opensearch-dashboards-security-factory.ts delete mode 100644 plugins/main/server/lib/security-factory/index.ts delete mode 100644 plugins/main/server/lib/security-factory/security-factory.ts delete mode 100644 plugins/main/server/lib/update-registry.ts delete mode 100644 plugins/main/test/cypress/cypress/integration/step-definitions/settings/wazuh-logs/reload-logs.when.js create mode 100644 plugins/wazuh-core/common/api-user-status-run-as.ts create mode 100644 plugins/wazuh-core/docs/README.md delete mode 100644 plugins/wazuh-core/server/controllers/index.ts delete mode 100644 plugins/wazuh-core/server/services/api-client.ts delete mode 100644 plugins/wazuh-core/server/services/api-interceptor.ts delete mode 100644 plugins/wazuh-core/server/services/base-logger.ts rename plugins/{main/server/lib/ui-logger.ts => wazuh-core/server/services/cookie.ts} (53%) delete mode 100644 plugins/wazuh-core/server/services/logger.ts create mode 100644 plugins/wazuh-core/server/services/server-api-client.ts rename plugins/wazuh-core/server/{controllers/wazuh-hosts.ts => services/server-api-host-entries.ts} (81%) rename plugins/{main/server/lib/update-configuration.ts => wazuh-core/server/services/update-configuration-file.ts} (69%) diff --git a/plugins/main/common/config-equivalences.js b/plugins/main/common/config-equivalences.js index 83aebea711..529a0346b2 100644 --- a/plugins/main/common/config-equivalences.js +++ b/plugins/main/common/config-equivalences.js @@ -37,7 +37,6 @@ export const configEquivalences = { 'wazuh.monitoring.pattern': 'Default index pattern to use for Wazuh monitoring.', hideManagerAlerts: 'Hide the alerts of the manager in every dashboard.', - 'logs.level': 'Logging level of the App.', 'enrollment.dns': 'Specifies the Wazuh registration server, used for the agent enrollment.', 'enrollment.password': @@ -85,7 +84,6 @@ export const nameEquivalence = { 'wazuh.monitoring.creation': 'Index creation', 'wazuh.monitoring.pattern': 'Index pattern', hideManagerAlerts: 'Hide manager alerts', - 'logs.level': 'Log level', 'enrollment.dns': 'Enrollment DNS', 'cron.prefix': 'Cron prefix', 'cron.statistics.status': 'Status', @@ -142,7 +140,6 @@ export const categoriesEquivalence = { 'wazuh.monitoring.creation': MONITORING, 'wazuh.monitoring.pattern': MONITORING, hideManagerAlerts: GENERAL, - 'logs.level': GENERAL, 'enrollment.dns': GENERAL, 'cron.prefix': GENERAL, 'cron.statistics.status': STATISTICS, @@ -197,15 +194,6 @@ export const formEquivalence = { }, 'wazuh.monitoring.pattern': { type: TEXT }, hideManagerAlerts: { type: BOOLEAN }, - 'logs.level': { - type: LIST, - params: { - options: [ - { text: 'Info', value: 'info' }, - { text: 'Debug', value: 'debug' }, - ], - }, - }, 'enrollment.dns': { type: TEXT }, 'cron.prefix': { type: TEXT }, 'cron.statistics.status': { type: BOOLEAN }, diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index 463a67395a..8fcce9ea44 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -140,35 +140,6 @@ export const WAZUH_DATA_CONFIG_REGISTRY_PATH = path.join( 'wazuh-registry.json', ); -// Wazuh data path - logs -export const MAX_MB_LOG_FILES = 100; -export const WAZUH_DATA_LOGS_DIRECTORY_PATH = path.join( - WAZUH_DATA_ABSOLUTE_PATH, - 'logs', -); -export const WAZUH_DATA_LOGS_PLAIN_FILENAME = 'wazuhapp-plain.log'; -export const WAZUH_DATA_LOGS_PLAIN_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_DATA_LOGS_PLAIN_FILENAME, -); -export const WAZUH_DATA_LOGS_RAW_FILENAME = 'wazuhapp.log'; -export const WAZUH_DATA_LOGS_RAW_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_DATA_LOGS_RAW_FILENAME, -); - -// Wazuh data path - UI logs -export const WAZUH_UI_LOGS_PLAIN_FILENAME = 'wazuh-ui-plain.log'; -export const WAZUH_UI_LOGS_RAW_FILENAME = 'wazuh-ui.log'; -export const WAZUH_UI_LOGS_PLAIN_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_UI_LOGS_PLAIN_FILENAME, -); -export const WAZUH_UI_LOGS_RAW_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_UI_LOGS_RAW_FILENAME, -); - // Wazuh data path - downloads export const WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH = path.join( WAZUH_DATA_ABSOLUTE_PATH, @@ -1506,38 +1477,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.boolean(); }, }, - 'logs.level': { - title: 'Log level', - description: 'Logging level of the App.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.select, - options: { - select: [ - { - text: 'Info', - value: 'info', - }, - { - text: 'Debug', - value: 'debug', - }, - ], - }, - defaultValue: 'info', - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: 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)), - ); - }, - }, pattern: { title: 'Index pattern', description: diff --git a/plugins/main/common/plugin-settings.test.ts b/plugins/main/common/plugin-settings.test.ts index 2ede6f322f..959f5f9d7d 100644 --- a/plugins/main/common/plugin-settings.test.ts +++ b/plugins/main/common/plugin-settings.test.ts @@ -151,9 +151,6 @@ describe('[settings] Input validation', () => { ${'ip.ignore'} | ${['test', 'test#']} | ${'It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} ${'ip.selector'} | ${true} | ${undefined} ${'ip.selector'} | ${''} | ${'It should be a boolean. Allowed values: true or false.'} - ${'logs.level'} | ${'info'} | ${undefined} - ${'logs.level'} | ${'debug'} | ${undefined} - ${'logs.level'} | ${''} | ${'Invalid value. Allowed values: info, debug.'} ${'pattern'} | ${'test'} | ${undefined} ${'pattern'} | ${'test*'} | ${undefined} ${'pattern'} | ${''} | ${'Value can not be empty.'} diff --git a/plugins/main/common/wazu-menu/wz-menu-settings.cy.ts b/plugins/main/common/wazu-menu/wz-menu-settings.cy.ts index 4d1007a72f..9b8fc0dc76 100644 --- a/plugins/main/common/wazu-menu/wz-menu-settings.cy.ts +++ b/plugins/main/common/wazu-menu/wz-menu-settings.cy.ts @@ -16,7 +16,6 @@ export enum WAZUH_MENU_SETTINGS_SECTIONS_CY_TEST_ID { MODULES = 'menuSettingsModulesLink', SAMPLE_DATA = 'menuSettingsSampleDataLink', CONFIGURATION = 'menuSettingsConfigurationLink', - LOGS = 'menuSettingsLogsLink', MISCELLANEOUS = 'menuSettingsMiscellaneousLink', ABOUT = 'menuSettingsAboutLink', } diff --git a/plugins/main/opensearch_dashboards.json b/plugins/main/opensearch_dashboards.json index c22c97b0b7..d2beb8d5a5 100644 --- a/plugins/main/opensearch_dashboards.json +++ b/plugins/main/opensearch_dashboards.json @@ -18,6 +18,7 @@ "opensearchDashboardsUtils", "opensearchDashboardsLegacy", "wazuhCheckUpdates", + "wazuhCore", "wazuhEndpoints" ], "optionalPlugins": [ diff --git a/plugins/main/package.json b/plugins/main/package.json index 0641b43211..9f1cc623d6 100644 --- a/plugins/main/package.json +++ b/plugins/main/package.json @@ -64,8 +64,7 @@ "react-cookie": "^4.0.3", "read-last-lines": "^1.7.2", "timsort": "^0.3.0", - "typescript": "^5.0.4", - "winston": "3.9.0" + "typescript": "^5.0.4" }, "devDependencies": { "@types/node-cron": "^2.0.3", @@ -89,4 +88,4 @@ "redux-mock-store": "^1.5.4", "swagger-client": "^3.19.11" } -} \ No newline at end of file +} diff --git a/plugins/main/public/components/security/main.tsx b/plugins/main/public/components/security/main.tsx index ed17f3667e..9d54666232 100644 --- a/plugins/main/public/components/security/main.tsx +++ b/plugins/main/public/components/security/main.tsx @@ -12,7 +12,6 @@ import { Users } from './users/users'; import { Roles } from './roles/roles'; import { Policies } from './policies/policies'; import { GenericRequest } from '../../react-services/generic-request'; -import { API_USER_STATUS_RUN_AS } from '../../../server/lib/cache-api-user-has-run-as'; import { AppState } from '../../react-services/app-state'; import { RolesMapping } from './roles-mapping/roles-mapping'; import { @@ -30,6 +29,7 @@ import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/typ import { getErrorOrchestrator } from '../../react-services/common-services'; import { getPluginDataPath } from '../../../common/plugin'; import { security } from '../../utils/applications'; +import { getWazuhCorePlugin } from '../../kibana-services'; const tabs = [ { @@ -128,16 +128,16 @@ export const WzSecurity = compose( const isNotRunAs = allowRunAs => { let runAsWarningTxt = ''; switch (allowRunAs) { - case API_USER_STATUS_RUN_AS.HOST_DISABLED: + case getWazuhCorePlugin().API_USER_STATUS_RUN_AS.HOST_DISABLED: runAsWarningTxt = `For the role mapping to take effect, enable run_as in ${getPluginDataPath( 'config/wazuh.yml', )} configuration file, restart the ${PLUGIN_PLATFORM_NAME} service and clear your browser cache and cookies.`; break; - case API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED: + case getWazuhCorePlugin().API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED: runAsWarningTxt = 'The role mapping has no effect because the current Wazuh API user has allow_run_as disabled.'; break; - case API_USER_STATUS_RUN_AS.ALL_DISABLED: + case getWazuhCorePlugin().API_USER_STATUS_RUN_AS.ALL_DISABLED: runAsWarningTxt = `For the role mapping to take effect, enable run_as in ${getPluginDataPath( 'config/wazuh.yml', )} configuration file and set the current Wazuh API user allow_run_as to true. Restart the ${PLUGIN_PLATFORM_NAME} service and clear your browser cache and cookies.`; @@ -174,7 +174,8 @@ export const WzSecurity = compose( {selectedTabId === 'roleMapping' && ( <> {allowRunAs !== undefined && - allowRunAs !== API_USER_STATUS_RUN_AS.ENABLED && + allowRunAs !== + getWazuhCorePlugin().API_USER_STATUS_RUN_AS.ENABLED && isNotRunAs(allowRunAs)} diff --git a/plugins/main/public/components/settings/api/api-table.js b/plugins/main/public/components/settings/api/api-table.js index 53e6056e33..607fbc416b 100644 --- a/plugins/main/public/components/settings/api/api-table.js +++ b/plugins/main/public/components/settings/api/api-table.js @@ -29,19 +29,21 @@ import { } from '@elastic/eui'; import { WzButtonPermissions } from '../../common/permissions/button'; import { AppState } from '../../../react-services/app-state'; -import { API_USER_STATUS_RUN_AS } from '../../../../server/lib/cache-api-user-has-run-as'; import { withErrorBoundary, withReduxProvider } from '../../common/hocs'; import { compose } from 'redux'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { getErrorOrchestrator } from '../../../react-services/common-services'; -import { getWazuhCheckUpdatesPlugin } from '../../../kibana-services'; +import { + getWazuhCheckUpdatesPlugin, + getWazuhCorePlugin, +} from '../../../kibana-services'; import { AvailableUpdatesFlyout } from './available-updates-flyout'; import { formatUIDate } from '../../../react-services/time-service'; export const ApiTable = compose( withErrorBoundary, - withReduxProvider + withReduxProvider, )( class ApiTable extends Component { constructor(props) { @@ -62,7 +64,9 @@ export const ApiTable = compose( async getApisAvailableUpdates(forceUpdate = false) { try { this.setState({ refreshingAvailableUpdates: true }); - const availableUpdates = await this.state.getAvailableUpdates(forceUpdate); + const availableUpdates = await this.state.getAvailableUpdates( + forceUpdate, + ); this.setState({ availableUpdates }); } catch (error) { const options = { @@ -73,7 +77,9 @@ export const ApiTable = compose( error: { error: error, message: error.message || error, - title: `Error checking available updates: ${error.message || error}`, + title: `Error checking available updates: ${ + error.message || error + }`, }, }; @@ -141,7 +147,12 @@ export const ApiTable = compose( refreshingEntries: false, }); } catch (error) { - if (error && error.data && error.data.message && error.data.code === 2001) { + if ( + error && + error.data && + error.data.message && + error.data.code === 2001 + ) { this.props.showAddApiWithInitialError(error); } } @@ -154,7 +165,7 @@ export const ApiTable = compose( async checkApi(api) { try { const entries = this.state.apiEntries; - const idx = entries.map((e) => e.id).indexOf(api.id); + const idx = entries.map(e => e.id).indexOf(api.id); try { await this.props.checkManager(api); entries[idx].status = 'online'; @@ -183,7 +194,9 @@ export const ApiTable = compose( error: { error: error, message: error.message || error, - title: `Error checking manager connection: ${error.message || error}`, + title: `Error checking manager connection: ${ + error.message || error + }`, }, }; @@ -213,13 +226,14 @@ export const ApiTable = compose( }, }; - const isLoading = this.state.refreshingEntries || this.state.refreshingAvailableUpdates; + const isLoading = + this.state.refreshingEntries || this.state.refreshingAvailableUpdates; const items = [ - ...this.state.apiEntries?.map((apiEntry) => { + ...this.state.apiEntries?.map(apiEntry => { const versionData = this.state.availableUpdates?.apis_available_updates?.find( - (apiAvailableUpdates) => apiAvailableUpdates.api_id === apiEntry.id + apiAvailableUpdates => apiAvailableUpdates.api_id === apiEntry.id, ) || {}; return { @@ -272,44 +286,56 @@ export const ApiTable = compose( name: 'Status', align: 'left', sortable: true, - render: (item) => { + render: item => { if (item) { return item === 'online' ? ( - + Online ) : item.status === 'down' ? ( - + - + Warning - + this.props.copyToClipBoard(item.downReason)} + color='primary' + iconType='questionInCircle' + aria-label='Info about the error' + onClick={() => + this.props.copyToClipBoard(item.downReason) + } /> ) : ( - + - + Offline - + this.props.copyToClipBoard(item.downReason)} + color='primary' + iconType='questionInCircle' + aria-label='Info about the error' + onClick={() => + this.props.copyToClipBoard(item.downReason) + } /> @@ -318,7 +344,7 @@ export const ApiTable = compose( } else { return ( - +   Checking ); @@ -346,26 +372,42 @@ export const ApiTable = compose( if (!this.state.refreshingAvailableUpdates) { return ( - + - + {getContent()} {item === 'availableUpdates' ? ( - View available updates

}> + View available updates

} + > this.setState({ apiAvailableUpdateDetails: api })} + aria-label='Availabe updates' + iconType='eye' + onClick={() => + this.setState({ apiAvailableUpdateDetails: api }) + } />
) : null} {item === 'error' && api.error?.detail ? ( - + - +   Checking ); @@ -393,20 +435,22 @@ export const ApiTable = compose( align: 'center', sortable: true, width: '80px', - render: (value) => { - return value === API_USER_STATUS_RUN_AS.ENABLED ? ( + render: value => { + return value === + getWazuhCorePlugin().API_USER_STATUS_RUN_AS.ENABLED ? ( - + - ) : value === API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED ? ( + ) : value === + getWazuhCorePlugin().API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED ? ( - + ) : ( '' @@ -415,15 +459,19 @@ export const ApiTable = compose( }, { name: 'Actions', - render: (item) => ( + render: item => ( Set as default

}} - iconType={item.id === this.props.currentDefault ? 'starFilled' : 'starEmpty'} - aria-label="Set as default" + iconType={ + item.id === this.props.currentDefault + ? 'starFilled' + : 'starEmpty' + } + aria-label='Set as default' onClick={async () => { const currentDefault = await this.props.setDefault(item); this.setState({ @@ -433,12 +481,12 @@ export const ApiTable = compose( />
- Check connection

}> + Check connection

}> await this.checkApi(item)} - color="success" + color='success' />
@@ -456,8 +504,8 @@ export const ApiTable = compose( return ( - - + + @@ -469,8 +517,8 @@ export const ApiTable = compose( this.props.showAddApi()} > @@ -478,26 +526,33 @@ export const ApiTable = compose( - await this.refresh()}> + await this.refresh()} + > Refresh await this.getApisAvailableUpdates(true)} > Check updates{' '} - + @@ -508,32 +563,34 @@ export const ApiTable = compose( - - From here you can manage and configure the API entries. You can also check their - connection and status. + + From here you can manage and configure the API entries. You + can also check their connection and status. this.setState({ apiAvailableUpdateDetails: undefined })} + onClose={() => + this.setState({ apiAvailableUpdateDetails: undefined }) + } /> ); } - } + }, ); ApiTable.propTypes = { diff --git a/plugins/main/public/components/settings/settings-logs/logs.js b/plugins/main/public/components/settings/settings-logs/logs.js deleted file mode 100644 index b173905e3d..0000000000 --- a/plugins/main/public/components/settings/settings-logs/logs.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Wazuh app - React component building the API entries table. - * - * Copyright (C) 2015-2022 Wazuh, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * Find more information about this on the LICENSE file. - */ - -import React, { Component } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiCodeBlock, - EuiPanel, - EuiPage, - EuiButtonEmpty, - EuiTitle, - EuiText, - EuiProgress, -} from '@elastic/eui'; - -import { formatUIDate } from '../../../react-services/time-service'; -import { withErrorBoundary } from '../../common/hocs'; -import { getPluginDataPath } from '../../../../common/plugin'; - -class SettingsLogs extends Component { - constructor(props) { - super(props); - this.state = { - logs: [], - refreshingEntries: false, - }; - this.HEIGHT_WITHOUT_CODE_EDITOR = 325; - } - - componentDidMount() { - this._isMounted = true; - this.refresh(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - async refresh() { - this.setState({ - refreshingEntries: true, - }); - const logs = await this.props.getLogs(); - this._isMounted && - this.setState({ - refreshingEntries: false, - logs, - }); - } - - formatDate(date) { - return formatUIDate(date) - .replace('-', '/') - .replace('T', ' ') - .replace('Z', '') - .split('.')[0]; - } - - getMessage(log) { - const data = log.data || log.message; - return typeof data === 'object' - ? data.message || JSON.stringify(data) - : data.toString(); - } - - render() { - let text = ''; - (this.state.logs || []).forEach(x => { - text = - text + - (this.formatDate(x.date) + - ' ' + - x.level.toUpperCase() + - ' ' + - this.getMessage(x) + - '\n'); - }); - return ( - - - - - - - -

App log messages

-
-
-
-
- - await this.refresh()} - > - Refresh - - -
- - Log file located at {getPluginDataPath('logs/wazuhapp.log')} - - {this.state.refreshingEntries && ( - - )} - {!this.state.refreshingEntries && ( -
- - {text} - -
- )} -
-
- ); - } -} - -export default withErrorBoundary(SettingsLogs); diff --git a/plugins/main/public/controllers/settings/index.js b/plugins/main/public/controllers/settings/index.js index 6908d4af39..34300f298a 100644 --- a/plugins/main/public/controllers/settings/index.js +++ b/plugins/main/public/controllers/settings/index.js @@ -14,7 +14,6 @@ import { ApiTable } from '../../components/settings/api/api-table'; import { AddApi } from '../../components/settings/api/add-api'; import { ApiIsDown } from '../../components/settings/api/api-is-down'; import { WzConfigurationSettings } from '../../components/settings/configuration/configuration'; -import SettingsLogs from '../../components/settings/settings-logs/logs'; import { SettingsMiscellaneous } from '../../components/settings/miscellaneous/miscellaneous'; import { WzSampleDataWrapper } from '../../components/add-modules-data/WzSampleDataWrapper'; import { getAngularModule } from '../../kibana-services'; @@ -24,7 +23,6 @@ const app = getAngularModule(); WzSampleDataWrapper.displayName = 'WzSampleDataWrapper'; WzConfigurationSettings.displayName = 'WzConfigurationSettings'; -SettingsLogs.displayName = 'SettingsLogs'; SettingsMiscellaneous.displayName = 'SettingsMiscellaneous'; ApiTable.displayName = 'ApiTable'; AddApi.displayName = 'AddApi'; @@ -35,7 +33,6 @@ app .controller('settingsController', SettingsController) .value('WzSampleDataWrapper', WzSampleDataWrapper) .value('WzConfigurationSettings', WzConfigurationSettings) - .value('SettingsLogs', SettingsLogs) .value('SettingsMiscelaneous', SettingsMiscellaneous) .value('ApiTable', ApiTable) .value('AddApi', AddApi) diff --git a/plugins/main/public/controllers/settings/settings.js b/plugins/main/public/controllers/settings/settings.js index b405243fb0..c5897f5733 100644 --- a/plugins/main/public/controllers/settings/settings.js +++ b/plugins/main/public/controllers/settings/settings.js @@ -156,9 +156,6 @@ export class SettingsController { this.settingsTabsProps = { clickAction: tab => { this.switchTab(tab, true); - if (tab === 'logs') { - this.refreshLogs(); - } }, selectedTab: this.tab || 'api', // Define tabs for Wazuh plugin settings application @@ -166,12 +163,6 @@ export class SettingsController { getWzCurrentAppID() === appSettings.id ? this.tabsConfiguration : null, wazuhConfig: this.wazuhConfig, }; - - this.settingsLogsProps = { - getLogs: async () => { - return await this.getAppLogs(); - }, - }; } /** @@ -432,37 +423,6 @@ export class SettingsController { else this.messageErrorUpdate = text; } - /** - * Returns Wazuh app logs - */ - async getAppLogs() { - try { - const logs = await this.genericReq.request('GET', '/utils/logs', {}); - this.$scope.$applyAsync(); - return logs.data.lastLogs.map(item => JSON.parse(item)); - } catch (error) { - const options = { - context: `${SettingsController.name}.getAppLogs`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - getErrorOrchestrator().handleError(options); - - return [ - { - date: new Date(), - level: 'error', - message: 'Error when loading logs', - }, - ]; - } - } - /** * Returns Wazuh app info */ @@ -482,10 +442,6 @@ export class SettingsController { const pattern = AppState.getCurrentPattern(); this.selectedIndexPattern = pattern || config['pattern']; - if (this.tab === 'logs') { - this.getAppLogs(); - } - this.getCurrentAPIIndex(); if ( (this.currentApiEntryIndex || this.currentApiEntryIndex === 0) && @@ -510,13 +466,6 @@ export class SettingsController { } } - /** - * This ask again for wazuh logs - */ - refreshLogs() { - return this.getAppLogs(); - } - /** * Checks if there are new APIs entries in the wazuh.yml */ diff --git a/plugins/main/public/controllers/settings/settings.test.ts b/plugins/main/public/controllers/settings/settings.test.ts index 88f1300e20..40b423cde9 100644 --- a/plugins/main/public/controllers/settings/settings.test.ts +++ b/plugins/main/public/controllers/settings/settings.test.ts @@ -1,10 +1,9 @@ -import { ApiCheck, AppState, formatUIDate } from '../../react-services'; +import { ApiCheck, formatUIDate } from '../../react-services'; import { SettingsController } from './settings'; import { ErrorHandler } from '../../react-services/error-management'; import { UI_LOGGER_LEVELS } from '../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../react-services/error-orchestrator/types'; -import { ManageHosts } from '../../../server/lib/manage-hosts'; import axios, { AxiosResponse } from 'axios'; jest.mock('../../react-services/time-service'); jest.mock('../../react-services/app-state'); @@ -155,8 +154,18 @@ describe('Settings Controller', () => { ); controller.getSettings = jest.fn().mockResolvedValue([]); // mocking manager hosts - apiEntries from wazuh.yml - const manageHosts = new ManageHosts(); - controller.apiEntries = await manageHosts.getHosts(); + + controller.apiEntries = [ + { + manager: { + url: 'https://wazuh.manager', + port: 55000, + username: 'wazuh-wui', + password: 'mypassword1-', + run_as: false, + }, + }, + ]; await controller.$onInit(); expect(mockedGetErrorOrchestrator.handleError).toBeCalledTimes(1); expect(mockedGetErrorOrchestrator.handleError).toBeCalledWith( diff --git a/plugins/main/public/factories/wazuh-config.js b/plugins/main/public/factories/wazuh-config.js index 97940ff949..5a08473755 100644 --- a/plugins/main/public/factories/wazuh-config.js +++ b/plugins/main/public/factories/wazuh-config.js @@ -31,11 +31,4 @@ export class WazuhConfig { getConfig() { return this.config; } - - /** - * Returns true if debug level is enabled, otherwise it returns false. - */ - isDebug() { - return ((this.config || {})['logs.level'] || false) === 'debug'; - } } diff --git a/plugins/main/public/kibana-services.ts b/plugins/main/public/kibana-services.ts index 1257f238ed..1c536dc5e1 100644 --- a/plugins/main/public/kibana-services.ts +++ b/plugins/main/public/kibana-services.ts @@ -45,6 +45,8 @@ export const [getWzCurrentAppID, setWzCurrentAppID] = createGetterSetter('WzCurrentAppID'); export const [getWazuhCheckUpdatesPlugin, setWazuhCheckUpdatesPlugin] = createGetterSetter('WazuhCheckUpdatesPlugin'); +export const [getWazuhCorePlugin, setWazuhCorePlugin] = + createGetterSetter('WazuhCorePlugin'); export const [getHeaderActionMenuMounter, setHeaderActionMenuMounter] = createGetterSetter( 'headerActionMenuMounter', diff --git a/plugins/main/public/plugin.ts b/plugins/main/public/plugin.ts index ab5ea70129..313a2edbc3 100644 --- a/plugins/main/public/plugin.ts +++ b/plugins/main/public/plugin.ts @@ -24,6 +24,7 @@ import { setWzCurrentAppID, setWazuhCheckUpdatesPlugin, setHeaderActionMenuMounter, + setWazuhCorePlugin, } from './kibana-services'; import { AppPluginStartDependencies, @@ -56,7 +57,10 @@ export class WazuhPlugin public initializeInnerAngular?: () => void; private innerAngularInitialized: boolean = false; private hideTelemetryBanner?: () => void; - public async setup(core: CoreSetup, plugins: WazuhSetupPlugins): WazuhSetup { + public async setup( + core: CoreSetup, + plugins: WazuhSetupPlugins, + ): Promise { // Get custom logos configuration to start up the app with the correct logos let logosInitialState = {}; try { @@ -221,6 +225,7 @@ export class WazuhPlugin setOverlays(core.overlays); setErrorOrchestrator(ErrorOrchestratorService); setWazuhCheckUpdatesPlugin(plugins.wazuhCheckUpdates); + setWazuhCorePlugin(plugins.wazuhCore); return {}; } } diff --git a/plugins/main/public/react-services/wazuh-config.js b/plugins/main/public/react-services/wazuh-config.js index 689ca382af..21f6dfe686 100644 --- a/plugins/main/public/react-services/wazuh-config.js +++ b/plugins/main/public/react-services/wazuh-config.js @@ -10,8 +10,8 @@ * Find more information about this on the LICENSE file. */ -import store from "../redux/store"; -import { updateAppConfig } from "../redux/actions/appConfigActions"; +import store from '../redux/store'; +import { updateAppConfig } from '../redux/actions/appConfigActions'; export class WazuhConfig { constructor() { @@ -20,7 +20,6 @@ export class WazuhConfig { } WazuhConfig.instance = this; - return this; } @@ -29,7 +28,7 @@ export class WazuhConfig { * @param {Object} cfg */ setConfig(cfg) { - store.dispatch(updateAppConfig({...cfg})); + store.dispatch(updateAppConfig({ ...cfg })); } /** @@ -38,11 +37,4 @@ export class WazuhConfig { getConfig() { return store.getState().appConfig.data; } - - /** - * Returns true if debug level is enabled, otherwise it returns false. - */ - isDebug() { - return ((this.getConfig() || {})['logs.level'] || false) === 'debug'; - } } diff --git a/plugins/main/public/templates/settings/settings.html b/plugins/main/public/templates/settings/settings.html index 04460ef309..3493e99bb0 100644 --- a/plugins/main/public/templates/settings/settings.html +++ b/plugins/main/public/templates/settings/settings.html @@ -1,7 +1,10 @@
- +
@@ -9,7 +12,10 @@ ng-if="!ctrl.load && ctrl.settingsTabsProps && !ctrl.apiIsDown && ctrl.apiTableProps.apiEntries.length && ctrl.settingsTabsProps.tabs" class="wz-margin-top-16 md-margin-h" > - +
@@ -17,17 +23,35 @@
- +
-
- +
+
-
- +
+
@@ -40,11 +64,6 @@ >
- -
- -
-
diff --git a/plugins/main/public/types.ts b/plugins/main/public/types.ts index d4edcbb66a..647791058f 100644 --- a/plugins/main/public/types.ts +++ b/plugins/main/public/types.ts @@ -5,13 +5,20 @@ import { VisualizationsSetup, VisualizationsStart, } from '../../../src/plugins/visualizations/public'; -import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../src/plugins/data/public'; +import { + DataPublicPluginSetup, + DataPublicPluginStart, +} from '../../../src/plugins/data/public'; import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; import { UiActionsSetup } from '../../../src/plugins/ui_actions/public'; import { SecurityOssPluginStart } from '../../../src/plugins/security_oss/public/'; import { SavedObjectsStart } from '../../../src/plugins/saved_objects/public'; -import { TelemetryPluginStart, TelemetryPluginSetup } from '../../../src/plugins/telemetry/public'; +import { + TelemetryPluginStart, + TelemetryPluginSetup, +} from '../../../src/plugins/telemetry/public'; import { WazuhCheckUpdatesPluginStart } from '../../wazuh-check-updates/public'; +import { WazuhCorePluginStart } from '../../wazuh-core/public'; import { WazuhEndpointsPluginStart } from '../../wazuh-endpoints/public'; import { DashboardStart } from '../../../src/plugins/dashboard/public'; @@ -25,6 +32,7 @@ export interface AppPluginStartDependencies { savedObjects: SavedObjectsStart; telemetry: TelemetryPluginStart; wazuhCheckUpdates: WazuhCheckUpdatesPluginStart; + wazuhCore: WazuhCorePluginStart; wazuhEndpoints: WazuhEndpointsPluginStart; dashboard: DashboardStart; } diff --git a/plugins/main/public/utils/applications.ts b/plugins/main/public/utils/applications.ts index aa675cd06b..bbf791d96b 100644 --- a/plugins/main/public/utils/applications.ts +++ b/plugins/main/public/utils/applications.ts @@ -713,22 +713,6 @@ export const appSettings = { redirectTo: () => '/settings?tab=configuration', }; -const appLogs = { - category: 'management', - id: 'app-logs', - title: i18n.translate('wz-app-app-logs-title', { - defaultMessage: 'App Logs', - }), - description: i18n.translate('wz-app-app-logs-description', { - defaultMessage: 'Explore the logs related to the applications.', - }), - euiIconType: 'indexRollupApp', - order: 8904, - showInOverviewApp: false, - showInAgentMenu: false, - redirectTo: () => '/settings?tab=logs', -}; - const about = { category: 'management', id: 'about', @@ -782,7 +766,6 @@ export const Applications = [ serverApis, sampleData, appSettings, - appLogs, about, ].sort((a, b) => { // Sort applications by order diff --git a/plugins/main/server/controllers/wazuh-api.ts b/plugins/main/server/controllers/wazuh-api.ts index cc7bf6cf69..0f366841ed 100644 --- a/plugins/main/server/controllers/wazuh-api.ts +++ b/plugins/main/server/controllers/wazuh-api.ts @@ -13,7 +13,6 @@ // Require some libraries import { ErrorResponse } from '../lib/error-response'; import { Parser } from 'json2csv'; -import { log } from '../lib/logger'; import { KeyEquivalence } from '../../common/csv-key-equivalence'; import { ApiErrorEquivalence } from '../lib/api-errors-equivalence'; import apiRequestList from '../../common/api-info/endpoints'; @@ -21,31 +20,17 @@ import { HTTP_STATUS_CODES } from '../../common/constants'; import { getCustomizationSetting } from '../../common/services/settings'; import { addJobToQueue } from '../start/queue'; import fs from 'fs'; -import { ManageHosts } from '../lib/manage-hosts'; -import { UpdateRegistry } from '../lib/update-registry'; import jwtDecode from 'jwt-decode'; import { OpenSearchDashboardsRequest, RequestHandlerContext, OpenSearchDashboardsResponseFactory, } from 'src/core/server'; -import { - APIUserAllowRunAs, - CacheInMemoryAPIUserAllowRunAs, - API_USER_STATUS_RUN_AS, -} from '../lib/cache-api-user-has-run-as'; import { getCookieValueByName } from '../lib/cookie'; -import { SecurityObj } from '../lib/security-factory'; import { getConfiguration } from '../lib/get-configuration'; export class WazuhApiCtrl { - manageHosts: ManageHosts; - updateRegistry: UpdateRegistry; - - constructor() { - this.manageHosts = new ManageHosts(); - this.updateRegistry = new UpdateRegistry(); - } + constructor() {} async getToken( context: RequestHandlerContext, @@ -82,14 +67,16 @@ export class WazuhApiCtrl { }); } } catch (error) { - log('wazuh-api:getToken', error.message || error); + context.wazuh.logger.error( + `Error decoding the API host entry token: ${error.message}`, + ); } } } let token; if ( - (await APIUserAllowRunAs.canUse(idHost)) == - API_USER_STATUS_RUN_AS.ENABLED + (await context.wazuh_core.cacheAPIUserAllowRunAs.canUse(idHost)) === + context.wazuh_core.cacheAPIUserAllowRunAs.API_USER_STATUS_RUN_AS.ENABLED ) { token = await context.wazuh.api.client.asCurrentUser.authenticate( idHost, @@ -116,11 +103,12 @@ export class WazuhApiCtrl { body: { token }, }); } catch (error) { - const errorMessage = - ((error.response || {}).data || {}).detail || error.message || error; - log('wazuh-api:getToken', errorMessage); + const errorMessage = `Error getting the authorization token: ${ + ((error.response || {}).data || {}).detail || error.message || error + }`; + context.wazuh.logger.error(errorMessage); return ErrorResponse( - `Error getting the authorization token: ${errorMessage}`, + errorMessage, 3000, error?.response?.status || HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR, response, @@ -143,13 +131,17 @@ export class WazuhApiCtrl { try { // Get config from wazuh.yml const id = request.body.id; - const api = await this.manageHosts.getHostById(id); + context.wazuh.logger.debug(`Getting server API host by ID: ${id}`); + const api = await context.wazuh_core.manageHosts.getHostById(id); + context.wazuh.logger.debug( + `Server API host data: ${JSON.stringify(api)}`, + ); // Check Manage Hosts if (!Object.keys(api).length) { - throw new Error('Could not find Wazuh API entry on wazuh.yml'); + throw new Error('Could not find server API entry in the configuration'); } - log('wazuh-api:checkStoredAPI', `${id} exists`, 'debug'); + context.wazuh.logger.debug(`${id} exists`); // Fetch needed information about the cluster and the manager itself const responseManagerInfo = @@ -161,7 +153,7 @@ export class WazuhApiCtrl { ); // Look for socket-related errors - if (this.checkResponseIsDown(responseManagerInfo)) { + if (this.checkResponseIsDown(context, responseManagerInfo)) { return ErrorResponse( `ERROR3099 - ${ responseManagerInfo.data.detail || 'Wazuh not ready yet' @@ -240,7 +232,10 @@ export class WazuhApiCtrl { if (api.cluster_info) { // Update cluster information in the wazuh-registry.json - await this.updateRegistry.updateClusterInfo(id, api.cluster_info); + await context.wazuh_core.updateRegistry.updateClusterInfo( + id, + api.cluster_info, + ); // Hide Wazuh API secret, username, password const copied = { ...api }; @@ -280,7 +275,7 @@ export class WazuhApiCtrl { }); } else { try { - const apis = await this.manageHosts.getHosts(); + const apis = await context.wazuh_core.manageHosts.getHosts(); for (const api of apis) { try { const id = Object.keys(api)[0]; @@ -293,7 +288,7 @@ export class WazuhApiCtrl { { apiHostID: id }, ); - if (this.checkResponseIsDown(responseManagerInfo)) { + if (this.checkResponseIsDown(context, responseManagerInfo)) { return ErrorResponse( `ERROR3099 - ${ response.data.detail || 'Wazuh not ready yet' @@ -311,7 +306,7 @@ export class WazuhApiCtrl { } catch (error) {} // eslint-disable-line } } catch (error) { - log('wazuh-api:checkStoredAPI', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( error.message || error, 3020, @@ -319,7 +314,7 @@ export class WazuhApiCtrl { response, ); } - log('wazuh-api:checkStoredAPI', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( error.message || error, 3002, @@ -374,15 +369,18 @@ export class WazuhApiCtrl { let apiAvailable = null; // const notValid = this.validateCheckApiParams(request.body); // if (notValid) return ErrorResponse(notValid, 3003, HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR, response); - log('wazuh-api:checkAPI', `${request.body.id} is valid`, 'debug'); + context.wazuh.logger.debug(`${request.body.id} is valid`); // Check if a Wazuh API id is given (already stored API) - const data = await this.manageHosts.getHostById(request.body.id); + const data = await context.wazuh_core.manageHosts.getHostById( + request.body.id, + ); if (data) { apiAvailable = data; } else { - log('wazuh-api:checkAPI', `API ${request.body.id} not found`); + const errorMessage = `The server API host entry with ID ${request.body.id} was not found`; + context.wazuh.logger.debug(errorMessage); return ErrorResponse( - `The API ${request.body.id} was not found`, + errorMessage, 3029, HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR, response, @@ -411,12 +409,7 @@ export class WazuhApiCtrl { response, ); } - - log( - 'wazuh-api:checkAPI', - `${request.body.id} credentials are valid`, - 'debug', - ); + context.wazuh.logger.debug(`${request.body.id} credentials are valid`); if ( responseManagerInfo.status === HTTP_STATUS_CODES.OK && responseManagerInfo.data @@ -442,7 +435,9 @@ export class WazuhApiCtrl { ); // Check the run_as for the API user and update it - let apiUserAllowRunAs = API_USER_STATUS_RUN_AS.ALL_DISABLED; + let apiUserAllowRunAs = + context.wazuh_core.cacheAPIUserAllowRunAs.API_USER_STATUS_RUN_AS + .ALL_DISABLED; const responseApiUserAllowRunAs = await context.wazuh.api.client.asInternalUser.request( 'GET', @@ -457,29 +452,33 @@ export class WazuhApiCtrl { if (allow_run_as && apiAvailable && apiAvailable.run_as) // HOST AND USER ENABLED - apiUserAllowRunAs = API_USER_STATUS_RUN_AS.ENABLED; + apiUserAllowRunAs = + context.wazuh_core.cacheAPIUserAllowRunAs.API_USER_STATUS_RUN_AS + .ENABLED; else if (!allow_run_as && apiAvailable && apiAvailable.run_as) // HOST ENABLED AND USER DISABLED - apiUserAllowRunAs = API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED; + apiUserAllowRunAs = + context.wazuh_core.cacheAPIUserAllowRunAs.API_USER_STATUS_RUN_AS + .USER_NOT_ALLOWED; else if (allow_run_as && (!apiAvailable || !apiAvailable.run_as)) // USER ENABLED AND HOST DISABLED - apiUserAllowRunAs = API_USER_STATUS_RUN_AS.HOST_DISABLED; + apiUserAllowRunAs = + context.wazuh_core.cacheAPIUserAllowRunAs.API_USER_STATUS_RUN_AS + .HOST_DISABLED; else if (!allow_run_as && (!apiAvailable || !apiAvailable.run_as)) // HOST AND USER DISABLED - apiUserAllowRunAs = API_USER_STATUS_RUN_AS.ALL_DISABLED; + apiUserAllowRunAs = + context.wazuh_core.cacheAPIUserAllowRunAs.API_USER_STATUS_RUN_AS + .ALL_DISABLED; } - CacheInMemoryAPIUserAllowRunAs.set( + context.wazuh_core.cacheAPIUserAllowRunAs.set( request.body.id, apiAvailable.username, apiUserAllowRunAs, ); if (responseCluster.status === HTTP_STATUS_CODES.OK) { - log( - 'wazuh-api:checkStoredAPI', - `Wazuh API response is valid`, - 'debug', - ); + context.wazuh.logger.debug('Wazuh API response is valid'); if (responseCluster.data.data.enabled === 'yes') { // If cluster mode is active let responseClusterLocal = @@ -517,7 +516,7 @@ export class WazuhApiCtrl { } } } catch (error) { - log('wazuh-api:checkAPI', error.message || error); + context.wazuh.logger.warn(error.message || error); if ( error && @@ -561,7 +560,7 @@ export class WazuhApiCtrl { } } - checkResponseIsDown(response) { + checkResponseIsDown(context, response) { if (response.status !== HTTP_STATUS_CODES.OK) { // Avoid "Error communicating with socket" like errors const socketErrorCodes = [1013, 1014, 1017, 1018, 1019]; @@ -569,8 +568,7 @@ export class WazuhApiCtrl { const isDown = socketErrorCodes.includes(status); isDown && - log( - 'wazuh-api:makeRequest', + context.wazuh.logger.error( 'Wazuh API is online but Wazuh is not ready yet', ); @@ -612,7 +610,7 @@ export class WazuhApiCtrl { const isValid = execd && modulesd && wazuhdb && clusterd; - isValid && log('wazuh-api:checkDaemons', `Wazuh is ready`, 'debug'); + isValid && context.wazuh.logger.debug('Wazuh is ready'); if (path === '/ping') { return { isValid }; @@ -622,7 +620,7 @@ export class WazuhApiCtrl { throw new Error('Wazuh not ready yet'); } } catch (error) { - log('wazuh-api:checkDaemons', error.message || error); + context.wazuh.logger.error(error.message || error); return Promise.reject(error); } } @@ -667,13 +665,13 @@ export class WazuhApiCtrl { async makeRequest(context, method, path, data, id, response) { const devTools = !!(data || {}).devTools; try { - const api = await this.manageHosts.getHostById(id); + const api = await context.wazuh_core.manageHosts.getHostById(id); if (devTools) { delete data.devTools; } if (!Object.keys(api).length) { - log('wazuh-api:makeRequest', 'Could not get host credentials'); + context.wazuh.logger.error('Could not get host credentials'); //Can not get credentials from wazuh-hosts return ErrorResponse( 'Could not get host credentials', @@ -723,7 +721,7 @@ export class WazuhApiCtrl { if (delay) { addJobToQueue({ startAt: new Date(Date.now() + delay), - run: async () => { + run: async contextJob => { try { await context.wazuh.api.client.asCurrentUser.request( method, @@ -732,8 +730,7 @@ export class WazuhApiCtrl { options, ); } catch (error) { - log( - 'queue:delayApiRequest', + contextJob.wazuh.logger.error( `An error ocurred in the delayed request: "${method} ${path}": ${ error.message || error }`, @@ -753,8 +750,7 @@ export class WazuhApiCtrl { } catch (error) { const isDown = (error || {}).code === 'ECONNREFUSED'; if (!isDown) { - log( - 'wazuh-api:makeRequest', + context.wazuh.logger.error( 'Wazuh API is online but Wazuh is not ready yet', ); return ErrorResponse( @@ -767,7 +763,7 @@ export class WazuhApiCtrl { } } - log('wazuh-api:makeRequest', `${method} ${path}`, 'debug'); + context.wazuh.logger.debug(`${method} ${path}`); // Extract keys from parameters const dataProperties = Object.keys(data); @@ -790,7 +786,7 @@ export class WazuhApiCtrl { data, options, ); - const responseIsDown = this.checkResponseIsDown(responseToken); + const responseIsDown = this.checkResponseIsDown(context, responseToken); if (responseIsDown) { return ErrorResponse( `ERROR3099 - ${response.body.message || 'Wazuh not ready yet'}`, @@ -841,7 +837,7 @@ export class WazuhApiCtrl { ); } const errorMsg = (error.response || {}).data || error.message; - log('wazuh-api:makeRequest', errorMsg || error); + context.wazuh.logger.error(errorMsg || error); if (devTools) { return response.ok({ body: { error: '3013', message: errorMsg || error }, @@ -890,7 +886,7 @@ export class WazuhApiCtrl { response, ); } else if (!request.body.method.match(/^(?:GET|PUT|POST|DELETE)$/)) { - log('wazuh-api:makeRequest', 'Request method is not valid.'); + context.wazuh.logger.error('Request method is not valid.'); //Method is not a valid HTTP request method return ErrorResponse( 'Request method is not valid.', @@ -906,7 +902,7 @@ export class WazuhApiCtrl { response, ); } else if (!request.body.path.startsWith('/')) { - log('wazuh-api:makeRequest', 'Request path is not valid.'); + context.wazuh.logger.error('Request path is not valid.'); //Path doesn't start with '/' return ErrorResponse( 'Request path is not valid.', @@ -955,7 +951,7 @@ export class WazuhApiCtrl { if (!tmpPath) throw new Error('An error occurred parsing path field'); - log('wazuh-api:csv', `Report ${tmpPath}`, 'debug'); + context.wazuh.logger.debug(`Report ${tmpPath}`); // Real limit, regardless the user query const params = { limit: 500 }; @@ -1088,7 +1084,7 @@ export class WazuhApiCtrl { ); } } catch (error) { - log('wazuh-api:csv', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( error.message || error, 3034, @@ -1124,13 +1120,11 @@ export class WazuhApiCtrl { ) { try { const source = JSON.parse( - fs.readFileSync(this.updateRegistry.file, 'utf8'), + fs.readFileSync(context.wazuh_core.updateRegistry.file, 'utf8'), ); if (source.installationDate && source.lastRestart) { - log( - 'wazuh-api:getTimeStamp', + context.wazuh.logger.debug( `Installation date: ${source.installationDate}. Last restart: ${source.lastRestart}`, - 'debug', ); return response.ok({ body: { @@ -1142,7 +1136,7 @@ export class WazuhApiCtrl { throw new Error('Could not fetch wazuh-version registry'); } } catch (error) { - log('wazuh-api:getTimeStamp', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( error.message || 'Could not fetch wazuh-version registry', 4001, @@ -1166,7 +1160,7 @@ export class WazuhApiCtrl { ) { try { const source = JSON.parse( - fs.readFileSync(this.updateRegistry.file, 'utf8'), + fs.readFileSync(context.wazuh_core.updateRegistry.file, 'utf8'), ); return response.ok({ body: { @@ -1175,7 +1169,7 @@ export class WazuhApiCtrl { }, }); } catch (error) { - log('wazuh-api:getSetupInfo', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( `Could not get data from wazuh-version registry due to ${ error.message || error @@ -1242,7 +1236,7 @@ export class WazuhApiCtrl { body: syscollector, }); } catch (error) { - log('wazuh-api:getSyscollector', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( error.message || error, 3035, @@ -1280,7 +1274,7 @@ export class WazuhApiCtrl { body: { logos }, }); } catch (error) { - log('wazuh-api:getAppLogos', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( error.message || error, 3035, diff --git a/plugins/main/server/controllers/wazuh-elastic.ts b/plugins/main/server/controllers/wazuh-elastic.ts index cdcfb23d41..63550e4900 100644 --- a/plugins/main/server/controllers/wazuh-elastic.ts +++ b/plugins/main/server/controllers/wazuh-elastic.ts @@ -10,7 +10,6 @@ * Find more information about this on the LICENSE file. */ import { ErrorResponse } from '../lib/error-response'; -import { log } from '../lib/logger'; import { getConfiguration } from '../lib/get-configuration'; import { AgentsVisualizations, @@ -25,13 +24,10 @@ import { WAZUH_SAMPLE_ALERTS_INDEX_REPLICAS, } from '../../common/constants'; import jwtDecode from 'jwt-decode'; -import { ManageHosts } from '../lib/manage-hosts'; import { OpenSearchDashboardsRequest, RequestHandlerContext, OpenSearchDashboardsResponseFactory, - SavedObject, - SavedObjectsFindResponse, } from 'src/core/server'; import { getCookieValueByName } from '../lib/cookie'; import { @@ -43,10 +39,8 @@ import { WAZUH_INDEXER_NAME } from '../../common/constants'; export class WazuhElasticCtrl { wzSampleAlertsIndexPrefix: string; - manageHosts: ManageHosts; constructor() { this.wzSampleAlertsIndexPrefix = this.getSampleAlertPrefix(); - this.manageHosts = new ManageHosts(); } /** @@ -125,14 +119,12 @@ export class WazuhElasticCtrl { item = lastChar === '*' ? item.slice(0, -1) : item; return item.includes(pattern) || pattern.includes(item); }); - log( - 'wazuh-elastic:getTemplate', + context.wazuh.logger.debug( `Template is valid: ${ isIncluded && Array.isArray(isIncluded) && isIncluded.length ? 'yes' : 'no' }`, - 'debug', ); return isIncluded && Array.isArray(isIncluded) && isIncluded.length ? response.ok({ @@ -150,7 +142,7 @@ export class WazuhElasticCtrl { }, }); } catch (error) { - log('wazuh-elastic:getTemplate', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( `Could not retrieve templates from ${WAZUH_INDEXER_NAME} due to ${ error.message || error @@ -254,7 +246,7 @@ export class WazuhElasticCtrl { }, }); } catch (error) { - log('wazuh-elastic:getFieldTop', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 4004, 500, response); } } @@ -333,7 +325,7 @@ export class WazuhElasticCtrl { }, }); } catch (error) { - log('wazuh-elastic:getCurrentPlatform', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 4011, 500, response); } } @@ -343,85 +335,68 @@ export class WazuhElasticCtrl { * @param {Array} app_objects Object containing raw visualizations. * @param {String} id Index-pattern id to use in the visualizations. Eg: 'wazuh-alerts' */ - async buildVisualizationsRaw(app_objects, id, namespace = false) { - try { - const config = getConfiguration(); - let monitoringPattern = - (config || {})['wazuh.monitoring.pattern'] || - getSettingDefaultValue('wazuh.monitoring.pattern'); - log( - 'wazuh-elastic:buildVisualizationsRaw', - `Building ${app_objects.length} visualizations`, - 'debug', - ); - log( - 'wazuh-elastic:buildVisualizationsRaw', - `Index pattern ID: ${id}`, - 'debug', - ); - const visArray = []; - let aux_source, bulk_content; - for (let element of app_objects) { - aux_source = JSON.parse(JSON.stringify(element._source)); - - // Replace index-pattern for visualizations - if ( - aux_source && - aux_source.kibanaSavedObjectMeta && - aux_source.kibanaSavedObjectMeta.searchSourceJSON && - typeof aux_source.kibanaSavedObjectMeta.searchSourceJSON === 'string' - ) { - const defaultStr = aux_source.kibanaSavedObjectMeta.searchSourceJSON; - - const isMonitoring = defaultStr.includes('wazuh-monitoring'); - if (isMonitoring) { - if (namespace && namespace !== 'default') { - if ( - monitoringPattern.includes(namespace) && - monitoringPattern.includes('index-pattern:') - ) { - monitoringPattern = - monitoringPattern.split('index-pattern:')[1]; - } + buildVisualizationsRaw(context, app_objects, id, namespace = false) { + const config = getConfiguration(); + let monitoringPattern = + (config || {})['wazuh.monitoring.pattern'] || + getSettingDefaultValue('wazuh.monitoring.pattern'); + context.wazuh.logger.debug(`Building ${app_objects.length} visualizations`); + context.wazuh.logger.debug(`Index pattern ID: ${id}`); + const visArray = []; + let aux_source, bulk_content; + for (let element of app_objects) { + aux_source = JSON.parse(JSON.stringify(element._source)); + + // Replace index-pattern for visualizations + if ( + aux_source && + aux_source.kibanaSavedObjectMeta && + aux_source.kibanaSavedObjectMeta.searchSourceJSON && + typeof aux_source.kibanaSavedObjectMeta.searchSourceJSON === 'string' + ) { + const defaultStr = aux_source.kibanaSavedObjectMeta.searchSourceJSON; + + const isMonitoring = defaultStr.includes('wazuh-monitoring'); + if (isMonitoring) { + if (namespace && namespace !== 'default') { + if ( + monitoringPattern.includes(namespace) && + monitoringPattern.includes('index-pattern:') + ) { + monitoringPattern = monitoringPattern.split('index-pattern:')[1]; } - aux_source.kibanaSavedObjectMeta.searchSourceJSON = - defaultStr.replace( - /wazuh-monitoring/g, - monitoringPattern[monitoringPattern.length - 1] === '*' || - (namespace && namespace !== 'default') - ? monitoringPattern - : monitoringPattern + '*', - ); - } else { - aux_source.kibanaSavedObjectMeta.searchSourceJSON = - defaultStr.replace(/wazuh-alerts/g, id); } + aux_source.kibanaSavedObjectMeta.searchSourceJSON = + defaultStr.replace( + /wazuh-monitoring/g, + monitoringPattern[monitoringPattern.length - 1] === '*' || + (namespace && namespace !== 'default') + ? monitoringPattern + : monitoringPattern + '*', + ); + } else { + aux_source.kibanaSavedObjectMeta.searchSourceJSON = + defaultStr.replace(/wazuh-alerts/g, id); } + } - // Replace index-pattern for selector visualizations - if (typeof (aux_source || {}).visState === 'string') { - aux_source.visState = aux_source.visState.replace( - /wazuh-alerts/g, - id, - ); - } + // Replace index-pattern for selector visualizations + if (typeof (aux_source || {}).visState === 'string') { + aux_source.visState = aux_source.visState.replace(/wazuh-alerts/g, id); + } - // Bulk source - bulk_content = {}; - bulk_content[element._type] = aux_source; + // Bulk source + bulk_content = {}; + bulk_content[element._type] = aux_source; - visArray.push({ - attributes: bulk_content.visualization, - type: element._type, - id: element._id, - _version: bulk_content.visualization.version, - }); - } - return visArray; - } catch (error) { - log('wazuh-elastic:buildVisualizationsRaw', error.message || error); - return Promise.reject(error); + visArray.push({ + attributes: bulk_content.visualization, + type: element._type, + id: element._id, + _version: bulk_content.visualization.version, + }); } + return visArray; } /** @@ -433,6 +408,7 @@ export class WazuhElasticCtrl { * @param {String} master_node Master node name. Eg: 'node01' */ buildClusterVisualizationsRaw( + context, app_objects, id, nodes = [], @@ -493,10 +469,7 @@ export class WazuhElasticCtrl { return visArray; } catch (error) { - log( - 'wazuh-elastic:buildClusterVisualizationsRaw', - error.message || error, - ); + context.wazuh.logger.error(error.message || error); return Promise.reject(error); } } @@ -539,12 +512,11 @@ export class WazuhElasticCtrl { }, }); } - log( - 'wazuh-elastic:createVis', + context.wazuh.logger.debug( `${tabPrefix}[${tabSufix}] with index pattern ${request.params.pattern}`, - 'debug', ); const raw = await this.buildVisualizationsRaw( + context, file, request.params.pattern, ); @@ -552,7 +524,7 @@ export class WazuhElasticCtrl { body: { acknowledge: true, raw: raw }, }); } catch (error) { - log('wazuh-elastic:createVis', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 4007, 500, response); } } @@ -596,6 +568,7 @@ export class WazuhElasticCtrl { const { id: patternID, title: patternName } = request.body.pattern; const raw = await this.buildClusterVisualizationsRaw( + context, file, patternID, nodes, @@ -608,7 +581,7 @@ export class WazuhElasticCtrl { body: { acknowledge: true, raw: raw }, }); } catch (error) { - log('wazuh-elastic:createClusterVis', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 4009, 500, response); } } @@ -673,8 +646,7 @@ export class WazuhElasticCtrl { body: { index: sampleAlertsIndex, exists: existsSampleIndex.body }, }); } catch (error) { - log( - 'wazuh-elastic:haveSampleAlertsOfCategory', + context.wazuh.logger.error( `Error checking if there are sample alerts indices: ${ error.message || error }`, @@ -795,28 +767,21 @@ export class WazuhElasticCtrl { index: sampleAlertsIndex, body: configuration, }); - log( - 'wazuh-elastic:createSampleAlerts', - `Created ${sampleAlertsIndex} index`, - 'debug', - ); + context.wazuh.logger.info(`Index ${sampleAlertsIndex} created`); } await context.core.opensearch.client.asCurrentUser.bulk({ index: sampleAlertsIndex, body: bulk, }); - log( - 'wazuh-elastic:createSampleAlerts', + context.wazuh.logger.info( `Added sample alerts to ${sampleAlertsIndex} index`, - 'debug', ); return response.ok({ body: { index: sampleAlertsIndex, alertCount: sampleAlerts.length }, }); } catch (error) { - log( - 'wazuh-elastic:createSampleAlerts', + context.wazuh.logger.error( `Error adding sample alerts to ${sampleAlertsIndex} index: ${ error.message || error }`, @@ -887,11 +852,7 @@ export class WazuhElasticCtrl { await context.core.opensearch.client.asCurrentUser.indices.delete({ index: sampleAlertsIndex, }); - log( - 'wazuh-elastic:deleteSampleAlerts', - `Deleted ${sampleAlertsIndex} index`, - 'debug', - ); + context.wazuh.logger.info(`Deleted ${sampleAlertsIndex} index`); return response.ok({ body: { result: 'deleted', index: sampleAlertsIndex }, }); @@ -904,8 +865,7 @@ export class WazuhElasticCtrl { ); } } catch (error) { - log( - 'wazuh-elastic:deleteSampleAlerts', + context.wazuh.logger.error( `Error deleting sample alerts of ${sampleAlertsIndex} index: ${ error.message || error }`, @@ -929,7 +889,7 @@ export class WazuhElasticCtrl { body: data.body, }); } catch (error) { - log('wazuh-elastic:alerts', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 4010, 500, response); } } @@ -954,7 +914,7 @@ export class WazuhElasticCtrl { body: existIndex.body, }); } catch (error) { - log('wazuh-elastic:existsStatisticsIndices', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 1000, 500, response); } } diff --git a/plugins/main/server/controllers/wazuh-hosts.ts b/plugins/main/server/controllers/wazuh-hosts.ts index 9d6a1d1779..52cc3cfcce 100644 --- a/plugins/main/server/controllers/wazuh-hosts.ts +++ b/plugins/main/server/controllers/wazuh-hosts.ts @@ -21,19 +21,10 @@ import { PLUGIN_PLATFORM_NAME, WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH, } from '../../common/constants'; -import { APIUserAllowRunAs } from '../lib/cache-api-user-has-run-as'; import { ErrorResponse } from '../lib/error-response'; -import { log } from '../lib/logger'; -import { ManageHosts } from '../lib/manage-hosts'; -import { UpdateRegistry } from '../lib/update-registry'; export class WazuhHostsCtrl { - manageHosts: ManageHosts; - updateRegistry: UpdateRegistry; - constructor() { - this.manageHosts = new ManageHosts(); - this.updateRegistry = new UpdateRegistry(); - } + constructor() {} /** * This get all hosts entries in the wazuh.yml and the related info in the wazuh-registry.json @@ -42,57 +33,23 @@ export class WazuhHostsCtrl { * @param {Object} response * API entries or ErrorResponse */ - async getHostsEntries(context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { + async getHostsEntries( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) { try { - const removePassword = true; - const hosts = await this.manageHosts.getHosts(); - const registry = await this.updateRegistry.getHosts(); - const result = await this.joinHostRegistry(hosts, registry, removePassword); + const result = + await context.wazuh_core.serverAPIHostEntries.getHostsEntries(); return response.ok({ - body: result + body: result, }); } catch (error) { - if(error && error.message && ['ENOENT: no such file or directory', WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH].every(text => error.message.includes(text))){ - return response.badRequest({ - body: { - message: `Error getting the hosts entries: The \'${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH}\' directory could not exist in your ${PLUGIN_PLATFORM_NAME} installation. - If this doesn't exist, create it and give the permissions 'sudo mkdir ${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH};sudo chown -R ${PLUGIN_PLATFORM_INSTALLATION_USER}:${PLUGIN_PLATFORM_INSTALLATION_USER_GROUP} ${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH}'. After, restart the ${PLUGIN_PLATFORM_NAME} service.` - } - }) - } - log('wazuh-hosts:getHostsEntries', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 2001, 500, response); } } - /** - * Joins the hosts with the related information in the registry - * @param {Object} hosts - * @param {Object} registry - * @param {Boolean} removePassword - */ - async joinHostRegistry(hosts: any, registry: any, removePassword: boolean = true) { - try { - if (!Array.isArray(hosts)) { - throw new Error('Hosts configuration error in wazuh.yml'); - } - - return await Promise.all(hosts.map(async h => { - const id = Object.keys(h)[0]; - const api = Object.assign(h[id], { id: id }); - const host = Object.assign(api, registry[id]); - // Add to run_as from API user. Use the cached value or get it doing a request - host.allow_run_as = await APIUserAllowRunAs.check(id); - if (removePassword) { - delete host.password; - delete host.token; - }; - return host; - })); - } catch (error) { - throw new Error(error); - } - } /** * This update an API hostname * @param {Object} context @@ -100,26 +57,31 @@ export class WazuhHostsCtrl { * @param {Object} response * Status response or ErrorResponse */ - async updateClusterInfo(context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { + async updateClusterInfo( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) { try { const { id } = request.params; const { cluster_info } = request.body; - await this.updateRegistry.updateClusterInfo(id, cluster_info); - log( - 'wazuh-hosts:updateClusterInfo', - `API entry ${id} hostname updated`, - 'debug' + await context.wazuh_core.updateRegistry.updateClusterInfo( + id, + cluster_info, ); + context.wazuh.logger.info(`Server API host entry ${id} updated`); return response.ok({ - body: { statusCode: 200, message: 'ok' } + body: { statusCode: 200, message: 'ok' }, }); } catch (error) { - log('wazuh-hosts:updateClusterInfo', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( - `Could not update data in wazuh-registry.json due to ${error.message || error}`, + `Could not update data in wazuh-registry.json due to ${ + error.message || error + }`, 2012, 500, - response + response, ); } } @@ -130,21 +92,27 @@ export class WazuhHostsCtrl { * @param {Object} request * @param {Object} response */ - async removeOrphanEntries(context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { + async removeOrphanEntries( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) { try { const { entries } = request.body; - log('wazuh-hosts:cleanRegistry', 'Cleaning registry', 'debug'); - await this.updateRegistry.removeOrphanEntries(entries); + context.wazuh.logger.debug('Cleaning registry file'); + await context.wazuh_core.updateRegistry.removeOrphanEntries(entries); return response.ok({ - body: { statusCode: 200, message: 'ok' } + body: { statusCode: 200, message: 'ok' }, }); } catch (error) { - log('wazuh-hosts:cleanRegistry', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse( - `Could not clean entries in the wazuh-registry.json due to ${error.message || error}`, + `Could not clean entries in the wazuh-registry.json due to ${ + error.message || error + }`, 2013, 500, - response + response, ); } } diff --git a/plugins/main/server/controllers/wazuh-reporting-security-endpoint-handler.test.ts b/plugins/main/server/controllers/wazuh-reporting-security-endpoint-handler.test.ts index 5e0c8c4e15..1a0d771df1 100644 --- a/plugins/main/server/controllers/wazuh-reporting-security-endpoint-handler.test.ts +++ b/plugins/main/server/controllers/wazuh-reporting-security-endpoint-handler.test.ts @@ -2,51 +2,62 @@ import md5 from 'md5'; import fs from 'fs'; import { WazuhReportingCtrl } from './wazuh-reporting'; -jest.mock('../lib/logger', () => ({ - log: jest.fn() -})); - jest.mock('../lib/reporting/extended-information', () => ({ extendedInformation: () => {}, - buildAgentsTable: () => {} + buildAgentsTable: () => {}, })); jest.mock('../lib/reporting/printer', () => { class ReportPrinterMock { - constructor() { } - addContent() { } - addConfigTables() { } - addTables() { } - addTimeRangeAndFilters() { } - addVisualizations() { } - formatDate() { } - checkTitle() { } - addSimpleTable() { } - addList() { } - addNewLine() { } - addContentWithNewLine() { } - addAgentsFilters() { } - print() { } + constructor() {} + addContent() {} + addConfigTables() {} + addTables() {} + addTimeRangeAndFilters() {} + addVisualizations() {} + formatDate() {} + checkTitle() {} + addSimpleTable() {} + addList() {} + addNewLine() {} + addContentWithNewLine() {} + addAgentsFilters() {} + print() {} } return { - ReportPrinter: ReportPrinterMock - } + ReportPrinter: ReportPrinterMock, + }; }); -const getMockerUserContext = (username: string) => ({ username, hashUsername: md5(username) }); +const getMockerUserContext = (username: string) => ({ + username, + hashUsername: md5(username), +}); const mockContext = (username: string) => ({ wazuh: { security: { - getCurrentUser: () => getMockerUserContext(username) - } - } + getCurrentUser: () => getMockerUserContext(username), + }, + logger: { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + get: jest.fn(() => ({ + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + })), + }, + }, }); const mockResponse = () => ({ - ok: (body) => body, - custom: (body) => body, - badRequest: (body) => body + ok: body => body, + custom: body => body, + badRequest: body => body, }); const endpointController = new WazuhReportingCtrl(); @@ -71,124 +82,155 @@ describe('[security] Report endpoints guard related to a file. Parameter defines }); it.each` - testTitle | username | filename | endpointProtected - ${'Execute endpoint handler'} | ${'admin'} | ${'wazuh-module-overview-general-1234.pdf'} | ${false} - ${'Endpoint protected'} | ${'admin'} | ${'../wazuh-module-overview-general-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'admin'} | ${'wazuh-module-overview-../general-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'admin'} | ${'custom../wazuh-module-overview-general-1234.pdf'} | ${true} - ${'Execute endpoint handler'} | ${'../../etc'} | ${'wazuh-module-agents-001-general-1234.pdf'} | ${false} - ${'Endpoint protected'} | ${'../../etc'} | ${'../wazuh-module-agents-001-general-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'../../etc'} | ${'wazuh-module-overview-../general-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'../../etc'} | ${'custom../wazuh-module-overview-general-1234.pdf'} | ${true} - `(`$testTitle + testTitle | username | filename | endpointProtected + ${'Execute endpoint handler'} | ${'admin'} | ${'wazuh-module-overview-general-1234.pdf'} | ${false} + ${'Endpoint protected'} | ${'admin'} | ${'../wazuh-module-overview-general-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'admin'} | ${'wazuh-module-overview-../general-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'admin'} | ${'custom../wazuh-module-overview-general-1234.pdf'} | ${true} + ${'Execute endpoint handler'} | ${'../../etc'} | ${'wazuh-module-agents-001-general-1234.pdf'} | ${false} + ${'Endpoint protected'} | ${'../../etc'} | ${'../wazuh-module-agents-001-general-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'../../etc'} | ${'wazuh-module-overview-../general-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'../../etc'} | ${'custom../wazuh-module-overview-general-1234.pdf'} | ${true} + `( + `$testTitle username: $username filename: $filename - endpointProtected: $endpointProtected`, async ({ username, filename, endpointProtected }) => { - const response = await endpointController.checkReportsUserDirectoryIsValidRouteDecorator( - routeHandler, - function getFilename(request) { - return request.params.name + endpointProtected: $endpointProtected`, + async ({ username, filename, endpointProtected }) => { + const response = + await endpointController.checkReportsUserDirectoryIsValidRouteDecorator( + routeHandler, + function getFilename(request) { + return request.params.name; + }, + )( + mockContext(username), + { params: { name: filename } }, + mockResponse(), + ); + if (endpointProtected) { + expect(response.body.message).toBe('5040 - You shall not pass!'); + expect(routeHandler.mock.calls).toHaveLength(0); + } else { + expect(routeHandler.mock.calls).toHaveLength(1); + expect(response).toBe(routeHandlerResponse); } - )(mockContext(username), { params: { name: filename } }, mockResponse()); - if (endpointProtected) { - - expect(response.body.message).toBe('5040 - You shall not pass!'); - expect(routeHandler.mock.calls).toHaveLength(0); - } else { - expect(routeHandler.mock.calls).toHaveLength(1); - expect(response).toBe(routeHandlerResponse); - } - }); - + }, + ); }); describe('[security] GET /reports', () => { - it.each` - username - ${'admin'} - ${'../../etc'} - `(`Get user reports: GET /reports - username: $username`, async ({ username }) => { - jest.spyOn(fs, 'readdirSync').mockImplementation(() => []); - - const response = await endpointController.getReports(mockContext(username), {}, mockResponse()); - expect(response.body.reports).toHaveLength(0); - }); + username + ${'admin'} + ${'../../etc'} + `( + `Get user reports: GET /reports + username: $username`, + async ({ username }) => { + jest.spyOn(fs, 'readdirSync').mockImplementation(() => []); + + const response = await endpointController.getReports( + mockContext(username), + {}, + mockResponse(), + ); + expect(response.body.reports).toHaveLength(0); + }, + ); }); describe('[security] GET /reports/{name}', () => { - it.each` - titleTest | username | filename | valid - ${'Get report'} | ${'admin'} | ${'wazuh-module-overview-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'admin'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} - ${'Get report'} | ${'../../etc'} | ${'wazuh-module-overview-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'../../etc'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} - `(`$titleTest: GET /reports/$filename + titleTest | username | filename | valid + ${'Get report'} | ${'admin'} | ${'wazuh-module-overview-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'admin'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} + ${'Get report'} | ${'../../etc'} | ${'wazuh-module-overview-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'../../etc'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} + `( + `$titleTest: GET /reports/$filename username: $username - valid: $valid`, async ({ username, filename, valid }) => { - const fileContent = 'content file'; - jest.spyOn(fs, 'readFileSync').mockImplementation(() => fileContent); - - const response = await endpointController.getReportByName(mockContext(username), { params: { name: filename } }, mockResponse()); - if (valid) { - expect(response.headers['Content-Type']).toBe('application/pdf'); - expect(response.body).toBe('content file'); - } else { - expect(response.body.message).toBe('5040 - You shall not pass!'); - } - }); + valid: $valid`, + async ({ username, filename, valid }) => { + const fileContent = 'content file'; + jest.spyOn(fs, 'readFileSync').mockImplementation(() => fileContent); + + const response = await endpointController.getReportByName( + mockContext(username), + { params: { name: filename } }, + mockResponse(), + ); + if (valid) { + expect(response.headers['Content-Type']).toBe('application/pdf'); + expect(response.body).toBe('content file'); + } else { + expect(response.body.message).toBe('5040 - You shall not pass!'); + } + }, + ); }); describe('[security] POST /reports', () => { jest.mock('../lib/filesystem', () => ({ - createDataDirectoryIfNotExists: jest.fn() + createDataDirectoryIfNotExists: jest.fn(), })); it.each` - titleTest | username | moduleID | valid - ${'Create report'} | ${'admin'} | ${'general'} | ${true} - ${'Endpoint protected'} | ${'admin'} | ${'../general'} | ${false} - ${'Create report'} | ${'../../etc'} | ${'general'} | ${true} - ${'Endpoint protected'} | ${'../../etc'} | ${'../general'} | ${false} - `(`$titleTest: POST /reports/modules/$moduleID + titleTest | username | moduleID | valid + ${'Create report'} | ${'admin'} | ${'general'} | ${true} + ${'Endpoint protected'} | ${'admin'} | ${'../general'} | ${false} + ${'Create report'} | ${'../../etc'} | ${'general'} | ${true} + ${'Endpoint protected'} | ${'../../etc'} | ${'../general'} | ${false} + `( + `$titleTest: POST /reports/modules/$moduleID username: $username - valid: $valid`, async ({ username, moduleID, valid }) => { - jest.spyOn(endpointController, 'renderHeader').mockImplementation(() => true); - jest.spyOn(endpointController, 'sanitizeKibanaFilters').mockImplementation(() => [false, false]); - - const mockRequest = { - body: { - array: [], - agents: false, - browserTimezone: '', - searchBar: '', - filters: [], - time: { - from: '', - to: '' + valid: $valid`, + async ({ username, moduleID, valid }) => { + jest + .spyOn(endpointController, 'renderHeader') + .mockImplementation(() => true); + jest + .spyOn(endpointController, 'sanitizeKibanaFilters') + .mockImplementation(() => [false, false]); + + const mockRequest = { + body: { + array: [], + agents: false, + browserTimezone: '', + searchBar: '', + filters: [], + time: { + from: '', + to: '', + }, + tables: [], + section: 'overview', + indexPatternTitle: 'wazuh-alerts-*', + apiId: 'default', + tab: moduleID, }, - tables: [], - section: 'overview', - indexPatternTitle: 'wazuh-alerts-*', - apiId: 'default', - tab: moduleID - }, - params: { - moduleID: moduleID - } - }; + params: { + moduleID: moduleID, + }, + }; - const response = await endpointController.createReportsModules(mockContext(username), mockRequest, mockResponse()); + const response = await endpointController.createReportsModules( + mockContext(username), + mockRequest, + mockResponse(), + ); - if (valid) { - expect(response.body.success).toBe(true); - expect(response.body.message).toMatch(new RegExp(`Report wazuh-module-overview-${moduleID}`)); - } else { - expect(response.body.message).toBe('5040 - You shall not pass!'); - }; - }); + if (valid) { + expect(response.body.success).toBe(true); + expect(response.body.message).toMatch( + new RegExp(`Report wazuh-module-overview-${moduleID}`), + ); + } else { + expect(response.body.message).toBe('5040 - You shall not pass!'); + } + }, + ); }); describe('[security] DELETE /reports/', () => { @@ -199,26 +241,35 @@ describe('[security] DELETE /reports/', () => { }); it.each` - titleTest | username | filename | valid - ${'Delete report'} | ${'admin'} | ${'wazuh-module-overview-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'admin'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} - ${'Endpoint protected'} | ${'admin'} | ${'custom../wazuh-module-overview-1234.pdf'}| ${false} - ${'Delete report'} | ${'../../etc'} | ${'wazuh-module-overview-1234.pdf'} | ${true} - ${'Endpoint protected'} | ${'../../etc'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} - ${'Endpoint protected'} | ${'../../etc'} | ${'custom../wazuh-module-overview-1234.pdf'}| ${false} - `(`[security] DELETE /reports/$filename + titleTest | username | filename | valid + ${'Delete report'} | ${'admin'} | ${'wazuh-module-overview-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'admin'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} + ${'Endpoint protected'} | ${'admin'} | ${'custom../wazuh-module-overview-1234.pdf'} | ${false} + ${'Delete report'} | ${'../../etc'} | ${'wazuh-module-overview-1234.pdf'} | ${true} + ${'Endpoint protected'} | ${'../../etc'} | ${'../wazuh-module-overview-1234.pdf'} | ${false} + ${'Endpoint protected'} | ${'../../etc'} | ${'custom../wazuh-module-overview-1234.pdf'} | ${false} + `( + `[security] DELETE /reports/$filename username: $username - valid: $valid`, async ({ filename, username, valid }) => { - mockFsUnlinkSync = jest.spyOn(fs, 'unlinkSync').mockImplementation(() => { }); - - const response = await endpointController.deleteReportByName(mockContext(username), { params: { name: filename } }, mockResponse()); - - if (valid) { - expect(response.body.error).toBe(0); - expect(mockFsUnlinkSync.mock.calls).toHaveLength(1); - } else { - expect(response.body.message).toBe('5040 - You shall not pass!'); - expect(mockFsUnlinkSync.mock.calls).toHaveLength(0); - }; - }); -}); \ No newline at end of file + valid: $valid`, + async ({ filename, username, valid }) => { + mockFsUnlinkSync = jest + .spyOn(fs, 'unlinkSync') + .mockImplementation(() => {}); + + const response = await endpointController.deleteReportByName( + mockContext(username), + { params: { name: filename } }, + mockResponse(), + ); + + if (valid) { + expect(response.body.error).toBe(0); + expect(mockFsUnlinkSync.mock.calls).toHaveLength(1); + } else { + expect(response.body.message).toBe('5040 - You shall not pass!'); + expect(mockFsUnlinkSync.mock.calls).toHaveLength(0); + } + }, + ); +}); diff --git a/plugins/main/server/controllers/wazuh-reporting-security-endpoint-parameters-validation.test.ts b/plugins/main/server/controllers/wazuh-reporting-security-endpoint-parameters-validation.test.ts index edd829d9fa..4aa002af30 100644 --- a/plugins/main/server/controllers/wazuh-reporting-security-endpoint-parameters-validation.test.ts +++ b/plugins/main/server/controllers/wazuh-reporting-security-endpoint-parameters-validation.test.ts @@ -5,8 +5,15 @@ import { ByteSizeValue } from '@osd/config-schema'; import supertest from 'supertest'; import { WazuhReportingRoutes } from '../routes/wazuh-reporting'; import md5 from 'md5'; -import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../lib/filesystem'; -import { WAZUH_DATA_ABSOLUTE_PATH, WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH } from '../../common/constants'; +import { + createDataDirectoryIfNotExists, + createDirectoryIfNotExists, +} from '../lib/filesystem'; +import { + WAZUH_DATA_ABSOLUTE_PATH, + WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, +} from '../../common/constants'; import { execSync } from 'child_process'; import path from 'path'; import fs from 'fs'; @@ -16,18 +23,25 @@ const logger = loggingService.get(); const context = { wazuh: { security: { - getCurrentUser: (request) => { + getCurrentUser: request => { // x-test-username header doesn't exist when the platform or plugin are running. // It is used to generate the output of this method so we can simulate the user // that does the request to the endpoint and is expected by the endpoint handlers // of the plugin. const username = request.headers['x-test-username']; - return { username, hashUsername: md5(username) } - } - } - } + return { username, hashUsername: md5(username) }; + }, + }, + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, + }, }; -const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, context); +const enhanceWithContext = (fn: (...args: any[]) => any) => + fn.bind(null, context); let server, innerServer; beforeAll(async () => { @@ -40,13 +54,29 @@ beforeAll(async () => { // Create report files [ { name: md5('admin'), files: ['wazuh-module-overview-general-1234.pdf'] }, - { name: md5('../../etc'), files: ['wazuh-module-overview-general-1234.pdf'] } + { + name: md5('../../etc'), + files: ['wazuh-module-overview-general-1234.pdf'], + }, ].forEach(({ name, files }) => { - createDirectoryIfNotExists(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name)); + createDirectoryIfNotExists( + path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name), + ); if (files) { - files.forEach(filename => fs.closeSync(fs.openSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name, filename), 'w'))); - }; + files.forEach(filename => + fs.closeSync( + fs.openSync( + path.join( + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, + name, + filename, + ), + 'w', + ), + ), + ); + } }); // Create server @@ -64,7 +94,11 @@ beforeAll(async () => { } as any; server = new HttpServer(loggingService, 'tests'); const router = new Router('', logger, enhanceWithContext); - const { registerRouter, server: innerServerTest, ...rest } = await server.setup(config); + const { + registerRouter, + server: innerServerTest, + ...rest + } = await server.setup(config); innerServer = innerServerTest; // Register routes @@ -87,202 +121,229 @@ afterAll(async () => { describe('[endpoint] GET /reports', () => { it.each` - username - ${'admin'} - ${'../../etc'} - `(`Get reports of user GET /reports - 200 - username: $username`, async ({ username }) => { - const response = await supertest(innerServer.listener) - .get('/reports') - .set('x-test-username', username) - .expect(200); + username + ${'admin'} + ${'../../etc'} + `( + `Get reports of user GET /reports - 200 + username: $username`, + async ({ username }) => { + const response = await supertest(innerServer.listener) + .get('/reports') + .set('x-test-username', username) + .expect(200); - expect(response.body.reports).toBeDefined(); - }); + expect(response.body.reports).toBeDefined(); + }, + ); }); describe('[endpoint][security] GET /reports/{name} - Parameters validation', () => { it.each` - testTitle | username | filename | responseStatusCode | responseBodyMessage - ${'Get report by filename'} | ${'admin'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} - ${'Invalid parameters'} | ${'admin'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Invalid parameters'} | ${'admin'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Route not found'} | ${'admin'} | ${'../custom..%2fwazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} - ${'Get report by filename'} | ${'../../etc'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} - ${'Invalid parameters'} | ${'../../etc'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Invalid parameters'} | ${'../../etc'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Route not found'} | ${'../../etc'} | ${'../custom..%2fwazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} - `(`$testTitle: GET /reports/$filename - responseStatusCode: $responseStatusCode + testTitle | username | filename | responseStatusCode | responseBodyMessage + ${'Get report by filename'} | ${'admin'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} + ${'Invalid parameters'} | ${'admin'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Invalid parameters'} | ${'admin'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Route not found'} | ${'admin'} | ${'../custom..%2fwazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} + ${'Get report by filename'} | ${'../../etc'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} + ${'Invalid parameters'} | ${'../../etc'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Invalid parameters'} | ${'../../etc'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Route not found'} | ${'../../etc'} | ${'../custom..%2fwazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} + `( + `$testTitle: GET /reports/$filename - responseStatusCode: $responseStatusCode username: $username - responseBodyMessage: $responseBodyMessage`, async ({ username, filename, responseStatusCode, responseBodyMessage }) => { - const response = await supertest(innerServer.listener) - .get(`/reports/${filename}`) - .set('x-test-username', username) - .expect(responseStatusCode); - if (responseStatusCode === 200) { - expect(response.header['content-type']).toMatch(/application\/pdf/); - expect(response.body instanceof Buffer).toBe(true); - }; - if (responseBodyMessage) { - expect(response.body.message).toMatch(responseBodyMessage); - }; - }); + responseBodyMessage: $responseBodyMessage`, + async ({ username, filename, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .get(`/reports/${filename}`) + .set('x-test-username', username) + .expect(responseStatusCode); + if (responseStatusCode === 200) { + expect(response.header['content-type']).toMatch(/application\/pdf/); + expect(response.body instanceof Buffer).toBe(true); + } + if (responseBodyMessage) { + expect(response.body.message).toMatch(responseBodyMessage); + } + }, + ); }); describe('[endpoint][security] POST /reports/modules/{moduleID} - Parameters validation', () => { it.each` - testTitle | username | moduleID | agents | responseStatusCode | responseBodyMessage - ${'Invalid paramenters'} | ${'admin'} | ${'..general'} | ${false} | ${400} | ${/\[request params.moduleID\]: types that failed validation:/} - ${'Route not found'} | ${'admin'} | ${'../general'} | ${false} | ${404} | ${/Not Found/} - ${'Route not found'} | ${'admin'} | ${'../general'} | ${'001'} | ${404} | ${/Not Found/} - ${'Invalid paramenters'} | ${'admin'} | ${'..%2fgeneral'} | ${'../001'} | ${400} | ${/\[request params.moduleID\]: types that failed validation:/} - ${'Invalid paramenters'} | ${'admin'} | ${'..%2fgeneral'} | ${'001'} | ${400} | ${/\[request params.moduleID\]: types that failed validation:/} - ${'Invalid paramenters'} | ${'admin'} | ${'general'} | ${'..001'} | ${400} | ${/\[request body.agents\]: types that failed validation:/} - ${'Invalid paramenters'} | ${'admin'} | ${'general'} | ${'../001'} | ${400} | ${/\[request body.agents\]: types that failed validation:/} - `(`$testTitle: GET /reports/modules/$moduleID - responseStatusCode: $responseStatusCode + testTitle | username | moduleID | agents | responseStatusCode | responseBodyMessage + ${'Invalid paramenters'} | ${'admin'} | ${'..general'} | ${false} | ${400} | ${/\[request params.moduleID\]: types that failed validation:/} + ${'Route not found'} | ${'admin'} | ${'../general'} | ${false} | ${404} | ${/Not Found/} + ${'Route not found'} | ${'admin'} | ${'../general'} | ${'001'} | ${404} | ${/Not Found/} + ${'Invalid paramenters'} | ${'admin'} | ${'..%2fgeneral'} | ${'../001'} | ${400} | ${/\[request params.moduleID\]: types that failed validation:/} + ${'Invalid paramenters'} | ${'admin'} | ${'..%2fgeneral'} | ${'001'} | ${400} | ${/\[request params.moduleID\]: types that failed validation:/} + ${'Invalid paramenters'} | ${'admin'} | ${'general'} | ${'..001'} | ${400} | ${/\[request body.agents\]: types that failed validation:/} + ${'Invalid paramenters'} | ${'admin'} | ${'general'} | ${'../001'} | ${400} | ${/\[request body.agents\]: types that failed validation:/} + `( + `$testTitle: GET /reports/modules/$moduleID - responseStatusCode: $responseStatusCode username: $username agents: $agents - responseBodyMessage: $responseBodyMessage`, async ({ username, moduleID, agents, responseStatusCode, responseBodyMessage }) => { - const response = await supertest(innerServer.listener) - .post(`/reports/modules/${moduleID}`) - .set('x-test-username', username) - .send({ - array: [], - agents: agents, - browserTimezone: '', - searchBar: '', - filters: [], - time: { - from: '', - to: '' - }, - tables: [], - section: 'overview', - indexPatternTitle: 'wazuh-alerts-*', - apiId: 'default' - }) - .expect(responseStatusCode); - if (responseBodyMessage) { - expect(response.body.message).toMatch(responseBodyMessage); - }; - }); + responseBodyMessage: $responseBodyMessage`, + async ({ + username, + moduleID, + agents, + responseStatusCode, + responseBodyMessage, + }) => { + const response = await supertest(innerServer.listener) + .post(`/reports/modules/${moduleID}`) + .set('x-test-username', username) + .send({ + array: [], + agents: agents, + browserTimezone: '', + searchBar: '', + filters: [], + time: { + from: '', + to: '', + }, + tables: [], + section: 'overview', + indexPatternTitle: 'wazuh-alerts-*', + apiId: 'default', + }) + .expect(responseStatusCode); + if (responseBodyMessage) { + expect(response.body.message).toMatch(responseBodyMessage); + } + }, + ); }); describe('[endpoint][security] POST /reports/groups/{groupID} - Parameters validation', () => { it.each` - testTitle | username | groupID | responseStatusCode | responseBodyMessage - ${'Invalid parameters'} | ${'admin'} | ${'..%2fdefault'} | ${400} | ${'[request params.groupID]: must be A-z, 0-9, _, . are allowed. It must not be ., .. or all.'} - ${'Route not found'} | ${'admin'} | ${'../default'} | ${404} | ${/Not Found/} - ${'Invalid parameters'} | ${'../../etc'} | ${'..%2fdefault'} | ${400} | ${'[request params.groupID]: must be A-z, 0-9, _, . are allowed. It must not be ., .. or all.'} - ${'Route not found'} | ${'../../etc'} | ${'../default'} | ${404} | ${/Not Found/} - `(`$testTitle: GET /reports/groups/$groupID - $responseStatusCode + testTitle | username | groupID | responseStatusCode | responseBodyMessage + ${'Invalid parameters'} | ${'admin'} | ${'..%2fdefault'} | ${400} | ${'[request params.groupID]: must be A-z, 0-9, _, . are allowed. It must not be ., .. or all.'} + ${'Route not found'} | ${'admin'} | ${'../default'} | ${404} | ${/Not Found/} + ${'Invalid parameters'} | ${'../../etc'} | ${'..%2fdefault'} | ${400} | ${'[request params.groupID]: must be A-z, 0-9, _, . are allowed. It must not be ., .. or all.'} + ${'Route not found'} | ${'../../etc'} | ${'../default'} | ${404} | ${/Not Found/} + `( + `$testTitle: GET /reports/groups/$groupID - $responseStatusCode username: $username - responseBodyMessage: $responseBodyMessage`, async ({ username, groupID, responseStatusCode, responseBodyMessage }) => { - const response = await supertest(innerServer.listener) - .post(`/reports/groups/${groupID}`) - .set('x-test-username', username) - .send({ - browserTimezone: '', - components: { '1': true }, - section: '', - apiId: 'default' - }) - .expect(responseStatusCode); - if (responseBodyMessage) { - expect(response.body.message).toMatch(responseBodyMessage); - }; - }); + responseBodyMessage: $responseBodyMessage`, + async ({ username, groupID, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .post(`/reports/groups/${groupID}`) + .set('x-test-username', username) + .send({ + browserTimezone: '', + components: { '1': true }, + section: '', + apiId: 'default', + }) + .expect(responseStatusCode); + if (responseBodyMessage) { + expect(response.body.message).toMatch(responseBodyMessage); + } + }, + ); }); describe('[endpoint][security] POST /reports/agents/{agentID} - Parameters validation', () => { it.each` - testTitle |username | agentID | responseStatusCode | responseBodyMessage - ${'Invalid parameters'} | ${'admin'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Route not found'} | ${'admin'} | ${'../001'} | ${404} | ${/Not Found/} - ${'Invalid parameters'} | ${'admin'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Invalid parameters'} | ${'admin'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} - ${'Invalid parameters'} | ${'../../etc'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Route not found'} | ${'../../etc'} | ${'../001'} | ${404} | ${/Not Found/} - ${'Invalid parameters'} | ${'../../etc'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Invalid parameters'} | ${'../../etc'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} - `(`$testTitle: GET /reports/agents/$agentID - $responseStatusCode + testTitle | username | agentID | responseStatusCode | responseBodyMessage + ${'Invalid parameters'} | ${'admin'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Route not found'} | ${'admin'} | ${'../001'} | ${404} | ${/Not Found/} + ${'Invalid parameters'} | ${'admin'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Invalid parameters'} | ${'admin'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} + ${'Invalid parameters'} | ${'../../etc'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Route not found'} | ${'../../etc'} | ${'../001'} | ${404} | ${/Not Found/} + ${'Invalid parameters'} | ${'../../etc'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Invalid parameters'} | ${'../../etc'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} + `( + `$testTitle: GET /reports/agents/$agentID - $responseStatusCode username: $username - responseBodyMessage: $responseBodyMessage`, async ({ username, agentID, responseStatusCode, responseBodyMessage }) => { - const response = await supertest(innerServer.listener) - .post(`/reports/agents/${agentID}`) - .set('x-test-username', username) - .send({ - array: [], - agents: agentID, - browserTimezone: '', - searchBar: '', - filters: [], - time: { - from: '', - to: '' - }, - tables: [], - section: 'overview', - indexPatternTitle: 'wazuh-alerts-*', - apiId: 'default' - }) - .expect(responseStatusCode); - if (responseBodyMessage) { - expect(response.body.message).toMatch(responseBodyMessage); - }; - }); + responseBodyMessage: $responseBodyMessage`, + async ({ username, agentID, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .post(`/reports/agents/${agentID}`) + .set('x-test-username', username) + .send({ + array: [], + agents: agentID, + browserTimezone: '', + searchBar: '', + filters: [], + time: { + from: '', + to: '', + }, + tables: [], + section: 'overview', + indexPatternTitle: 'wazuh-alerts-*', + apiId: 'default', + }) + .expect(responseStatusCode); + if (responseBodyMessage) { + expect(response.body.message).toMatch(responseBodyMessage); + } + }, + ); }); describe('[endpoint][security] POST /reports/agents/{agentID}/inventory - Parameters validation', () => { it.each` - testTitle | username | agentID | responseStatusCode | responseBodyMessage - ${'Invalid parameters'} | ${'admin'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Route not found'} | ${'admin'} | ${'../001'} | ${404} | ${/Not Found/} - ${'Invalid parameters'} | ${'admin'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Invalid parameters'} | ${'admin'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} - ${'Invalid parameters'} | ${'../../etc'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Route not found'} | ${'../../etc'} | ${'../001'} | ${404} | ${/Not Found/} - ${'Invalid parameters'} | ${'../../etc'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} - ${'Invalid parameters'} | ${'../../etc'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} - `(`$testTitle: GET /reports/agents/$agentID/inventory - $responseStatusCode + testTitle | username | agentID | responseStatusCode | responseBodyMessage + ${'Invalid parameters'} | ${'admin'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Route not found'} | ${'admin'} | ${'../001'} | ${404} | ${/Not Found/} + ${'Invalid parameters'} | ${'admin'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Invalid parameters'} | ${'admin'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} + ${'Invalid parameters'} | ${'../../etc'} | ${'..001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Route not found'} | ${'../../etc'} | ${'../001'} | ${404} | ${/Not Found/} + ${'Invalid parameters'} | ${'../../etc'} | ${'..%2f001'} | ${400} | ${/\[request params.agentID\]: must be 0-9 are allowed/} + ${'Invalid parameters'} | ${'../../etc'} | ${'1'} | ${400} | ${/\[request params.agentID\]: value has length \[1\] but it must have a minimum length of \[3\]./} + `( + `$testTitle: GET /reports/agents/$agentID/inventory - $responseStatusCode username: $username - responseBodyMessage: $responseBodyMessage`, async ({ username, agentID, responseStatusCode, responseBodyMessage }) => { - const response = await supertest(innerServer.listener) - .post(`/reports/agents/${agentID}/inventory`) - .set('x-test-username', username) - .send({ - browserTimezone: '', - components: { '1': true }, - section: '', - apiId: 'default' - }) - .expect(responseStatusCode); - if (responseBodyMessage) { - expect(response.body.message).toMatch(responseBodyMessage); - }; - }); + responseBodyMessage: $responseBodyMessage`, + async ({ username, agentID, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .post(`/reports/agents/${agentID}/inventory`) + .set('x-test-username', username) + .send({ + browserTimezone: '', + components: { '1': true }, + section: '', + apiId: 'default', + }) + .expect(responseStatusCode); + if (responseBodyMessage) { + expect(response.body.message).toMatch(responseBodyMessage); + } + }, + ); }); describe('[endpoint][security] DELETE /reports/{name} - Parameters validation', () => { it.each` - testTitle | username | filename | responseStatusCode | responseBodyMessage - ${'Delete report file'} | ${'admin'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} - ${'Invalid parameters'} | ${'admin'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Invalid parameters'} | ${'admin'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Route not found'} | ${'admin'} | ${'../wazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} - ${'Delete report file'} | ${'../../etc'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} - ${'Invalid parameters'} | ${'../../etc'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Invalid parameters'} | ${'../../etc'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} - ${'Route not found'} | ${'../../etc'} | ${'../wazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} - `(`$testTitle: DELETE /reports/$filename - $responseStatusCode + testTitle | username | filename | responseStatusCode | responseBodyMessage + ${'Delete report file'} | ${'admin'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} + ${'Invalid parameters'} | ${'admin'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Invalid parameters'} | ${'admin'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Route not found'} | ${'admin'} | ${'../wazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} + ${'Delete report file'} | ${'../../etc'} | ${'wazuh-module-overview-general-1234.pdf'} | ${200} | ${null} + ${'Invalid parameters'} | ${'../../etc'} | ${'..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Invalid parameters'} | ${'../../etc'} | ${'custom..%2fwazuh-module-overview-general-1234.pdf'} | ${400} | ${'[request params.name]: must be A-z, 0-9, _, ., and - are allowed. It must end with .pdf.'} + ${'Route not found'} | ${'../../etc'} | ${'../wazuh-module-overview-general-1234.pdf'} | ${404} | ${/Not Found/} + `( + `$testTitle: DELETE /reports/$filename - $responseStatusCode username: $username - responseBodyMessage: $responseBodyMessage`, async ({ username, filename, responseStatusCode, responseBodyMessage }) => { - const response = await supertest(innerServer.listener) - .delete(`/reports/${filename}`) - .set('x-test-username', username) - .expect(responseStatusCode); - if (responseBodyMessage) { - expect(response.body.message).toMatch(responseBodyMessage); - }; - }); -}); \ No newline at end of file + responseBodyMessage: $responseBodyMessage`, + async ({ username, filename, responseStatusCode, responseBodyMessage }) => { + const response = await supertest(innerServer.listener) + .delete(`/reports/${filename}`) + .set('x-test-username', username) + .expect(responseStatusCode); + if (responseBodyMessage) { + expect(response.body.message).toMatch(responseBodyMessage); + } + }, + ); +}); diff --git a/plugins/main/server/controllers/wazuh-reporting.ts b/plugins/main/server/controllers/wazuh-reporting.ts index 1f9d5c2a06..5c00d692b0 100644 --- a/plugins/main/server/controllers/wazuh-reporting.ts +++ b/plugins/main/server/controllers/wazuh-reporting.ts @@ -27,7 +27,6 @@ import { buildAgentsTable, } from '../lib/reporting/extended-information'; import { ReportPrinter } from '../lib/reporting/printer'; -import { log } from '../lib/logger'; import { WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, @@ -46,25 +45,19 @@ interface AgentsFilter { } export class WazuhReportingCtrl { - constructor() { } + constructor() {} /** * This do format to filters * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability * @param {String} searchBar search term */ private sanitizeKibanaFilters( + context: any, filters: any, searchBar?: string, ): [string, AgentsFilter] { - log( - 'reporting:sanitizeKibanaFilters', - `Started to sanitize filters`, - 'info', - ); - log( - 'reporting:sanitizeKibanaFilters', - `filters: ${filters.length}, searchBar: ${searchBar}`, - 'debug', + context.wazuh.logger.debug( + `Started to sanitize filters. filters: ${filters.length}, searchBar: ${searchBar}`, ); let str = ''; @@ -109,10 +102,8 @@ export class WazuhReportingCtrl { .map(filter => filter.meta.value) .join(','); - log( - 'reporting:sanitizeKibanaFilters', + context.wazuh.logger.debug( `str: ${str}, agentsFilterStr: ${agentsFilter.agentsText}`, - 'debug', ); return [str, agentsFilter]; @@ -128,10 +119,8 @@ export class WazuhReportingCtrl { */ private async renderHeader(context, printer, section, tab, isAgents, apiId) { try { - log( - 'reporting:renderHeader', + context.wazuh.logger.debug( `section: ${section}, tab: ${tab}, isAgents: ${isAgents}, apiId: ${apiId}`, - 'debug', ); if (section && typeof section === 'string') { if (!['agentConfig', 'groupConfig'].includes(section)) { @@ -199,13 +188,13 @@ export class WazuhReportingCtrl { }); } } catch (error) { - log('reporting:renderHeader', error.message || error); + context.wazuh.logger.error(error.message || error); return Promise.reject(error); } } - private getConfigRows(data, labels) { - log('reporting:getConfigRows', `Building configuration rows`, 'info'); + private getConfigRows(context, data, labels) { + context.wazuh.logger.debug('Building configuration rows'); const result = []; for (let prop in data || []) { if (Array.isArray(data[prop])) { @@ -221,8 +210,8 @@ export class WazuhReportingCtrl { return result; } - private getConfigTables(data, section, tab, array = []) { - log('reporting:getConfigTables', `Building configuration tables`, 'info'); + private getConfigTables(context, data, section, tab, array = []) { + context.wazuh.logger.debug('Building configuration tables'); let plainData = {}; const nestedData = []; const tableData = []; @@ -259,10 +248,10 @@ export class WazuhReportingCtrl { title: (section.options || {}).hideHeader ? '' : (section.tabs || [])[tab] || - (section.isGroupConfig ? ((section.labels || [])[0] || [])[tab] : ''), + (section.isGroupConfig ? ((section.labels || [])[0] || [])[tab] : ''), columns: ['', ''], type: 'config', - rows: this.getConfigRows(plainData, (section.labels || [])[0]), + rows: this.getConfigRows(context, plainData, (section.labels || [])[0]), }); for (let key in tableData) { const columns = Object.keys(tableData[key][0]); @@ -296,7 +285,7 @@ export class WazuhReportingCtrl { }); } nestedData.forEach(nest => { - this.getConfigTables(nest, section, tab + 1, array); + this.getConfigTables(context, nest, section, tab + 1, array); }); return array; } @@ -315,7 +304,7 @@ export class WazuhReportingCtrl { response: OpenSearchDashboardsResponseFactory, ) => { try { - log('reporting:createReportsModules', `Report started`, 'info'); + context.wazuh.logger.debug('Report started'); const { array, agents, @@ -333,7 +322,9 @@ export class WazuhReportingCtrl { const { from, to } = time || {}; let additionalTables = []; // Init - const printer = new ReportPrinter(); + const printer = new ReportPrinter( + context.wazuh.logger.get('report-printer'), + ); createDataDirectoryIfNotExists(); createDirectoryIfNotExists(WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH); @@ -355,7 +346,7 @@ export class WazuhReportingCtrl { ); const [sanitizedFilters, agentsFilter] = filters - ? this.sanitizeKibanaFilters(filters, searchBar) + ? this.sanitizeKibanaFilters(context, filters, searchBar) : [false, null]; if (time && sanitizedFilters) { @@ -426,11 +417,13 @@ export class WazuhReportingCtrl { response: OpenSearchDashboardsResponseFactory, ) => { try { - log('reporting:createReportsGroups', `Report started`, 'info'); + context.wazuh.logger.debug('Report started'); const { components, apiId } = request.body; const { groupID } = request.params; // Init - const printer = new ReportPrinter(); + const printer = new ReportPrinter( + context.wazuh.logger.get('report-printer'), + ); createDataDirectoryIfNotExists(); createDirectoryIfNotExists(WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH); @@ -594,7 +587,9 @@ export class WazuhReportingCtrl { }); } else { for (let _d2 of config.config[_d]) { - tables.push(...this.getConfigTables(_d2, section, idx)); + tables.push( + ...this.getConfigTables(context, _d2, section, idx), + ); } } } else { @@ -603,7 +598,12 @@ export class WazuhReportingCtrl { const directories = config.config[_d].directories; delete config.config[_d].directories; tables.push( - ...this.getConfigTables(config.config[_d], section, idx), + ...this.getConfigTables( + context, + config.config[_d], + section, + idx, + ), ); let diffOpts = []; Object.keys(section.opts).forEach(x => { @@ -640,7 +640,12 @@ export class WazuhReportingCtrl { }); } else { tables.push( - ...this.getConfigTables(config.config[_d], section, idx), + ...this.getConfigTables( + context, + config.config[_d], + section, + idx, + ), ); } } @@ -682,7 +687,7 @@ export class WazuhReportingCtrl { }, }); } catch (error) { - log('reporting:createReportsGroups', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 5029, 500, response); } }, @@ -705,15 +710,13 @@ export class WazuhReportingCtrl { response: OpenSearchDashboardsResponseFactory, ) => { try { - log( - 'reporting:createReportsAgentsConfiguration', - `Report started`, - 'info', - ); + context.wazuh.logger.debug('Report started'); const { components, apiId } = request.body; const { agentID } = request.params; - const printer = new ReportPrinter(); + const printer = new ReportPrinter( + context.wazuh.logger.get('report-printer'), + ); createDataDirectoryIfNotExists(); createDirectoryIfNotExists(WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH); createDirectoryIfNotExists( @@ -737,7 +740,7 @@ export class WazuhReportingCtrl { { apiHostID: apiId }, ); } catch (error) { - log('reporting:report', error.message || error, 'debug'); + context.wazuh.logger.debug(error.message || error); } await this.renderHeader( @@ -752,10 +755,8 @@ export class WazuhReportingCtrl { let idxComponent = 0; for (let config of AgentConfiguration.configurations) { let titleOfSection = false; - log( - 'reporting:createReportsAgentsConfiguration', + context.wazuh.logger.debug( `Iterate over ${config.sections.length} configuration sections`, - 'debug', ); for (let section of config.sections) { let titleOfSubsection = false; @@ -767,10 +768,8 @@ export class WazuhReportingCtrl { const configs = (section.config || []).concat( section.wodle || [], ); - log( - 'reporting:createReportsAgentsConfiguration', + context.wazuh.logger.debug( `Iterate over ${configs.length} configuration blocks`, - 'debug', ); for (let conf of configs) { let agentConfigResponse = {}; @@ -875,6 +874,7 @@ export class WazuhReportingCtrl { ) { tables.push( ...this.getConfigTables( + context, agentConfig[agentConfigKey], section, idx, @@ -883,7 +883,12 @@ export class WazuhReportingCtrl { } else { for (let _d2 of agentConfig[agentConfigKey]) { tables.push( - ...this.getConfigTables(_d2, section, idx), + ...this.getConfigTables( + context, + _d2, + section, + idx, + ), ); } } @@ -898,9 +903,15 @@ export class WazuhReportingCtrl { ...rest } = agentConfig[agentConfigKey]; tables.push( - ...this.getConfigTables(rest, section, idx), + ...this.getConfigTables( + context, + rest, + section, + idx, + ), ...(diff && diff.disk_quota ? this.getConfigTables( + context, diff.disk_quota, { tabs: ['Disk quota'] }, 0, @@ -908,6 +919,7 @@ export class WazuhReportingCtrl { : []), ...(diff && diff.file_size ? this.getConfigTables( + context, diff.file_size, { tabs: ['File size'] }, 0, @@ -915,6 +927,7 @@ export class WazuhReportingCtrl { : []), ...(synchronization ? this.getConfigTables( + context, synchronization, { tabs: ['Synchronization'] }, 0, @@ -922,6 +935,7 @@ export class WazuhReportingCtrl { : []), ...(file_limit ? this.getConfigTables( + context, file_limit, { tabs: ['File limit'] }, 0, @@ -965,6 +979,7 @@ export class WazuhReportingCtrl { } else { tables.push( ...this.getConfigTables( + context, agentConfig[agentConfigKey], section, idx, @@ -988,7 +1003,7 @@ export class WazuhReportingCtrl { }); } } catch (error) { - log('reporting:report', error.message || error, 'debug'); + context.wazuh.logger.debug(error.message || error); } idx++; } @@ -1010,10 +1025,7 @@ export class WazuhReportingCtrl { }, }); } catch (error) { - log( - 'reporting:createReportsAgentsConfiguration', - error.message || error, - ); + context.wazuh.logger.debug(error.message || error); return ErrorResponse(error.message || error, 5029, 500, response); } }, @@ -1036,11 +1048,7 @@ export class WazuhReportingCtrl { response: OpenSearchDashboardsResponseFactory, ) => { try { - log( - 'reporting:createReportsAgentsInventory', - `Report started`, - 'info', - ); + context.wazuh.logger.debug('Report started'); const { searchBar, filters, @@ -1052,7 +1060,9 @@ export class WazuhReportingCtrl { const { agentID } = request.params; const { from, to } = time || {}; // Init - const printer = new ReportPrinter(); + const printer = new ReportPrinter( + context.wazuh.logger.get('report-printer'), + ); const { hashUsername } = await context.wazuh.security.getCurrentUser( request, @@ -1070,13 +1080,9 @@ export class WazuhReportingCtrl { ), ); - log( - 'reporting:createReportsAgentsInventory', - `Syscollector report`, - 'debug', - ); + context.wazuh.logger.debug('Syscollector report'); const [sanitizedFilters, agentsFilter] = filters - ? this.sanitizeKibanaFilters(filters, searchBar) + ? this.sanitizeKibanaFilters(context, filters, searchBar) : [false, null]; // Get the agent OS @@ -1101,11 +1107,7 @@ export class WazuhReportingCtrl { agentOs = (isAgentWindows && 'windows') || (isAgentLinux && 'linux') || ''; } catch (error) { - log( - 'reporting:createReportsAgentsInventory', - error.message || error, - 'debug', - ); + context.wazuh.logger.debug(error.message || error); } // Add title @@ -1244,11 +1246,7 @@ export class WazuhReportingCtrl { const requestInventory = async agentRequestInventory => { try { - log( - 'reporting:createReportsAgentsInventory', - agentRequestInventory.loggerMessage, - 'debug', - ); + context.wazuh.logger.debug(agentRequestInventory.loggerMessage); const inventoryResponse = await context.wazuh.api.client.asCurrentUser.request( @@ -1272,11 +1270,7 @@ export class WazuhReportingCtrl { }; } } catch (error) { - log( - 'reporting:createReportsAgentsInventory', - error.message || error, - 'debug', - ); + context.wazuh.logger.debug(error.message || error); } }; @@ -1320,7 +1314,7 @@ export class WazuhReportingCtrl { }, }); } catch (error) { - log('reporting:createReportsAgents', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 5029, 500, response); } }, @@ -1341,7 +1335,7 @@ export class WazuhReportingCtrl { response: OpenSearchDashboardsResponseFactory, ) { try { - log('reporting:getReports', `Fetching created reports`, 'info'); + context.wazuh.logger.debug('Fetching created reports'); const { hashUsername } = await context.wazuh.security.getCurrentUser( request, context, @@ -1354,11 +1348,7 @@ export class WazuhReportingCtrl { hashUsername, ); createDirectoryIfNotExists(userReportsDirectoryPath); - log( - 'reporting:getReports', - `Directory: ${userReportsDirectoryPath}`, - 'debug', - ); + context.wazuh.logger.debug(`Directory: ${userReportsDirectoryPath}`); const sortReportsByDate = (a, b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0; @@ -1376,18 +1366,16 @@ export class WazuhReportingCtrl { date: stats[birthTimeField], }; }); - log( - 'reporting:getReports', + context.wazuh.logger.debug( `Using TimSort for sorting ${reports.length} items`, - 'debug', ); TimSort.sort(reports, sortReportsByDate); - log('reporting:getReports', `Total reports: ${reports.length}`, 'debug'); + context.wazuh.logger.debug(`Total reports: ${reports.length}`); return response.ok({ body: { reports }, }); } catch (error) { - log('reporting:getReports', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 5031, 500, response); } } @@ -1406,10 +1394,8 @@ export class WazuhReportingCtrl { response: OpenSearchDashboardsResponseFactory, ) => { try { - log( - 'reporting:getReportByName', + context.wazuh.logger.debug( `Getting ${context.wazuhEndpointParams.pathFilename} report`, - 'debug', ); const reportFileBuffer = fs.readFileSync( context.wazuhEndpointParams.pathFilename, @@ -1419,7 +1405,7 @@ export class WazuhReportingCtrl { body: reportFileBuffer, }); } catch (error) { - log('reporting:getReportByName', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 5030, 500, response); } }, @@ -1440,22 +1426,18 @@ export class WazuhReportingCtrl { response: OpenSearchDashboardsResponseFactory, ) => { try { - log( - 'reporting:deleteReportByName', + context.wazuh.logger.debug( `Deleting ${context.wazuhEndpointParams.pathFilename} report`, - 'debug', ); fs.unlinkSync(context.wazuhEndpointParams.pathFilename); - log( - 'reporting:deleteReportByName', + context.wazuh.logger.info( `${context.wazuhEndpointParams.pathFilename} report was deleted`, - 'info', ); return response.ok({ body: { error: 0 }, }); } catch (error) { - log('reporting:deleteReportByName', error.message || error); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 5032, 500, response); } }, @@ -1480,19 +1462,15 @@ export class WazuhReportingCtrl { ); const filename = reportFileNameAccessor(request); const pathFilename = path.join(userReportsDirectoryPath, filename); - log( - 'reporting:checkReportsUserDirectoryIsValidRouteDecorator', + context.wazuh.logger.debug( `Checking the user ${username}(${hashUsername}) can do actions in the reports file: ${pathFilename}`, - 'debug', ); if ( !pathFilename.startsWith(userReportsDirectoryPath) || pathFilename.includes('../') ) { - log( - 'security:reporting:checkReportsUserDirectoryIsValidRouteDecorator', + context.wazuh.logger.warn( `User ${username}(${hashUsername}) tried to access to a non user report file: ${pathFilename}`, - 'warn', ); return response.badRequest({ body: { @@ -1500,10 +1478,8 @@ export class WazuhReportingCtrl { }, }); } - log( - 'reporting:checkReportsUserDirectoryIsValidRouteDecorator', + context.wazuh.logger.debug( 'Checking the user can do actions in the reports file', - 'debug', ); return await routeHandler.bind(this)( { @@ -1514,10 +1490,7 @@ export class WazuhReportingCtrl { response, ); } catch (error) { - log( - 'reporting:checkReportsUserDirectoryIsValidRouteDecorator', - error.message || error, - ); + context.wazuh.logger.error(error.message || error); return ErrorResponse(error.message || error, 5040, 500, response); } }; diff --git a/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.test.ts b/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.test.ts index 8828560c61..71235b33d7 100644 --- a/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.test.ts +++ b/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.test.ts @@ -1,8 +1,21 @@ import { UiLogsCtrl } from './ui-logs.controller'; -import { WAZUH_UI_LOGS_RAW_PATH } from '../../../common/constants'; -import uiLogger from '../../lib/ui-logger'; -const readLastLines = require('read-last-lines'); +const buildMockContext = () => { + return { + wazuh: { + logger: { + get() { + return { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + }, + }, + }, + }; +}; const buildMockResponse = () => { const res = {}; @@ -19,26 +32,13 @@ const buildMockRequest = () => { describe('Spec UiLogsCtrl', function () { describe('Check method getUiLogs ', () => { - it('Should 200 and return correct value', async () => { - const result = { body: { error: 0, rawLogs: ['my test mocked'] } }; - const mockResponse = buildMockResponse(); - jest.spyOn(readLastLines, 'read').mockReturnValue('my test mocked'); - jest.spyOn(uiLogger, 'checkFileExist').mockReturnValue(true); - - const controller = new UiLogsCtrl(); - await controller.getUiLogs(mockResponse); - - expect(mockResponse.ok).toHaveBeenCalledTimes(1); - expect(mockResponse.ok.mock.calls.length).toBe(1); - expect(mockResponse.ok).toHaveBeenCalledWith(result); - }); - it('Should 200 and return message Log has been added', async () => { - const result = { body: { error: 0, message: 'Log has been added', statusCode: 200 } }; - const mockResponse = buildMockResponse(); - jest.spyOn(readLastLines, 'read').mockReturnValue('Log has been added'); - jest.spyOn(uiLogger, 'checkFileExist').mockReturnValue(true); + const result = { + body: { error: 0, message: 'Log has been added', statusCode: 200 }, + }; + const mockContext = buildMockContext(); + const mockResponse = buildMockResponse(); const mockRequest = buildMockRequest(); mockRequest.body = { level: 'error', @@ -47,18 +47,11 @@ describe('Spec UiLogsCtrl', function () { }; const controller = new UiLogsCtrl(); - await controller.createUiLogs(mockRequest, mockResponse); + await controller.createUiLogs(mockContext, mockRequest, mockResponse); expect(mockResponse.ok).toHaveBeenCalledTimes(1); expect(mockResponse.ok.mock.calls.length).toBe(1); expect(mockResponse.ok).toHaveBeenCalledWith(result); }); - - it('Should return a Array logs', async () => { - const controller = new UiLogsCtrl(); - let res = await controller.getUiFileLogs(WAZUH_UI_LOGS_RAW_PATH); - - expect(Array.isArray(res)).toBe(true); - }); }); }); diff --git a/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.ts b/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.ts index 76afd26add..64fda22920 100644 --- a/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.ts +++ b/plugins/main/server/controllers/wazuh-utils/ui-logs.controller.ts @@ -12,10 +12,11 @@ // Require some libraries import { ErrorResponse } from '../../lib/error-response'; -import { read } from 'read-last-lines'; -import { WAZUH_UI_LOGS_RAW_PATH } from '../../../common/constants'; -import { OpenSearchDashboardsRequest, OpenSearchDashboardsResponseFactory } from 'src/core/server'; -import uiLogger from '../../lib/ui-logger'; +import { + OpenSearchDashboardsRequest, + OpenSearchDashboardsResponseFactory, + RequestHandlerContext, +} from 'src/core/server'; export class UiLogsCtrl { /** @@ -25,45 +26,22 @@ export class UiLogsCtrl { constructor() {} /** - * Returns Wazuh ui logs - * @param {Object} response - * @returns {Array} app logs or ErrorResponse - */ - async getUiLogs(response: OpenSearchDashboardsResponseFactory) { - try { - return uiLogger.initDirectory().then(async () => { - if (!uiLogger.checkFileExist(WAZUH_UI_LOGS_RAW_PATH)) { - return response.ok({ - body: { - error: 0, - rawLogs: [], - }, - }); - } else { - let arrayLog = await this.getUiFileLogs(WAZUH_UI_LOGS_RAW_PATH); - return response.ok({ - body: { - error: 0, - rawLogs: arrayLog.filter((item) => typeof item === 'string' && item.length), - }, - }); - } - }); - } catch (error) { - return ErrorResponse(error.message || error, 3036, 500, response); - } - } - - /** - * Add new UI Log entry in ui logs file + * Add new UI Log entry to the platform logs + * @param context * @param request * @param response * @returns success message or ErrorResponse */ - async createUiLogs(request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { + async createUiLogs( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) { try { const { location, message, level } = request.body; - await uiLogger.log(location, message, level); + const loggerUI = context.wazuh.logger.get('ui'); + const loggerByLevel = loggerUI?.[level] || loggerUI.error; + loggerByLevel(`${location}: ${message}`); return response.ok({ body: { statusCode: 200, @@ -75,18 +53,4 @@ export class UiLogsCtrl { return ErrorResponse(error.message || error, 3021, 500, response); } } - - /** - * Get UI logs from specific log file - * @param filepath - * @returns Array - */ - async getUiFileLogs(filepath) { - try { - const lastLogs = await read(filepath, 50); - return lastLogs.split('\n'); - } catch (err) { - throw err; - } - } } diff --git a/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts b/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts index dfaba3b656..e3a7f856e4 100644 --- a/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts +++ b/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts @@ -14,11 +14,16 @@ import { ErrorResponse } from '../../lib/error-response'; import { getConfiguration } from '../../lib/get-configuration'; import { read } from 'read-last-lines'; -import { UpdateConfigurationFile } from '../../lib/update-configuration'; import jwtDecode from 'jwt-decode'; -import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_DATA_LOGS_RAW_PATH, PLUGIN_SETTINGS } from '../../../common/constants'; -import { ManageHosts } from '../../lib/manage-hosts'; -import { OpenSearchDashboardsRequest, RequestHandlerContext, OpenSearchDashboardsResponseFactory } from 'src/core/server'; +import { + WAZUH_ROLE_ADMINISTRATOR_ID, + PLUGIN_SETTINGS, +} from '../../../common/constants'; +import { + OpenSearchDashboardsRequest, + RequestHandlerContext, + OpenSearchDashboardsResponseFactory, +} from 'src/core/server'; import { getCookieValueByName } from '../../lib/cookie'; import fs from 'fs'; import path from 'path'; @@ -26,17 +31,13 @@ import { createDirectoryIfNotExists } from '../../lib/filesystem'; import glob from 'glob'; import { getFileExtensionFromBuffer } from '../../../common/services/file-extension'; -const updateConfigurationFile = new UpdateConfigurationFile(); - // TODO: these controllers have no logs. We should include them. export class WazuhUtilsCtrl { /** * Constructor * @param {*} server */ - constructor() { - this.manageHosts = new ManageHosts(); - } + constructor() {} /** * Returns the wazuh.yml file parsed @@ -45,7 +46,11 @@ export class WazuhUtilsCtrl { * @param {Object} response * @returns {Object} Configuration File or ErrorResponse */ - getConfigurationFile(context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { + getConfigurationFile( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) { try { const configFile = getConfiguration(); @@ -53,8 +58,8 @@ export class WazuhUtilsCtrl { body: { statusCode: 200, error: 0, - data: configFile || {} - } + data: configFile || {}, + }, }); } catch (error) { return ErrorResponse(error.message || error, 3019, 500, response); @@ -68,64 +73,74 @@ export class WazuhUtilsCtrl { * @param {Object} response * @returns {Object} Configuration File or ErrorResponse */ - updateConfigurationFile = this.routeDecoratorProtectedAdministratorRoleValidToken( - async (context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) => { - - let requiresRunningHealthCheck: boolean = false, - requiresReloadingBrowserTab: boolean = false, - requiresRestartingPluginPlatform: boolean = false; - - // Plugin settings configurables in the configuration file. - const pluginSettingsConfigurableFile = Object.keys(request.body) - .filter(pluginSettingKey => PLUGIN_SETTINGS[pluginSettingKey].isConfigurableFromFile) - .reduce((accum, pluginSettingKey: string) => ({ ...accum, [pluginSettingKey]: request.body[pluginSettingKey] }), {}); + updateConfigurationFile = + this.routeDecoratorProtectedAdministratorRoleValidToken( + async ( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) => { + let requiresRunningHealthCheck: boolean = false, + requiresReloadingBrowserTab: boolean = false, + requiresRestartingPluginPlatform: boolean = false; - if (Object.keys(pluginSettingsConfigurableFile).length) { - // Update the configuration file. - await updateConfigurationFile.updateConfiguration(pluginSettingsConfigurableFile); + // Plugin settings configurables in the configuration file. + const pluginSettingsConfigurableFile = Object.keys(request.body) + .filter( + pluginSettingKey => + PLUGIN_SETTINGS[pluginSettingKey].isConfigurableFromFile, + ) + .reduce( + (accum, pluginSettingKey: string) => ({ + ...accum, + [pluginSettingKey]: request.body[pluginSettingKey], + }), + {}, + ); - requiresRunningHealthCheck = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresRunningHealthCheck)) || requiresRunningHealthCheck; - requiresReloadingBrowserTab = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresReloadingBrowserTab)) || requiresReloadingBrowserTab; - requiresRestartingPluginPlatform = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresRestartingPluginPlatform)) || requiresRestartingPluginPlatform; - }; + if (Object.keys(pluginSettingsConfigurableFile).length) { + // Update the configuration file. + await context.wazuh_core.updateConfigurationFile.updateConfiguration( + pluginSettingsConfigurableFile, + ); - return response.ok({ - body: { - data: { requiresRunningHealthCheck, requiresReloadingBrowserTab, requiresRestartingPluginPlatform, updatedConfiguration: pluginSettingsConfigurableFile } + requiresRunningHealthCheck = + Object.keys(pluginSettingsConfigurableFile).some( + (pluginSettingKey: string) => + Boolean( + PLUGIN_SETTINGS[pluginSettingKey].requiresRunningHealthCheck, + ), + ) || requiresRunningHealthCheck; + requiresReloadingBrowserTab = + Object.keys(pluginSettingsConfigurableFile).some( + (pluginSettingKey: string) => + Boolean( + PLUGIN_SETTINGS[pluginSettingKey].requiresReloadingBrowserTab, + ), + ) || requiresReloadingBrowserTab; + requiresRestartingPluginPlatform = + Object.keys(pluginSettingsConfigurableFile).some( + (pluginSettingKey: string) => + Boolean( + PLUGIN_SETTINGS[pluginSettingKey] + .requiresRestartingPluginPlatform, + ), + ) || requiresRestartingPluginPlatform; } - }); - }, - 3021 - ) - /** - * Returns Wazuh app logs - * @param {Object} context - * @param {Object} request - * @param {Object} response - * @returns {Array} app logs or ErrorResponse - */ - async getAppLogs(context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { - try { - const lastLogs = await read( - WAZUH_DATA_LOGS_RAW_PATH, - 50 - ); - const spliterLog = lastLogs.split('\n'); - return spliterLog && Array.isArray(spliterLog) - ? response.ok({ + return response.ok({ body: { - error: 0, - lastLogs: spliterLog.filter( - item => typeof item === 'string' && item.length - ) - } - }) - : response.ok({ error: 0, lastLogs: [] }); - } catch (error) { - return ErrorResponse(error.message || error, 3036, 500, response); - } - } + data: { + requiresRunningHealthCheck, + requiresReloadingBrowserTab, + requiresRestartingPluginPlatform, + updatedConfiguration: pluginSettingsConfigurableFile, + }, + }, + }); + }, + 3021, + ); /** * Upload a file @@ -135,7 +150,11 @@ export class WazuhUtilsCtrl { * @returns {Object} Configuration File or ErrorResponse */ uploadFile = this.routeDecoratorProtectedAdministratorRoleValidToken( - async (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => { + async ( + context: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory, + ) => { const { key } = request.params; const { file: bufferFile } = request.body; const pluginSetting = PLUGIN_SETTINGS[key]; @@ -144,16 +163,24 @@ export class WazuhUtilsCtrl { const fileExtension = getFileExtensionFromBuffer(bufferFile); // Check if the extension is valid for the setting. - if (!pluginSetting.options.file.extensions.includes(`.${fileExtension}`)) { + if ( + !pluginSetting.options.file.extensions.includes(`.${fileExtension}`) + ) { return response.badRequest({ - body: `File extension is not valid for setting [${key}] setting. Allowed file extensions: ${pluginSetting.options.file.extensions.join(', ')}` + body: `File extension is not valid for setting [${key}] setting. Allowed file extensions: ${pluginSetting.options.file.extensions.join( + ', ', + )}`, }); - }; + } const fileNamePath = `${key}.${fileExtension}`; // Create target directory - const targetDirectory = path.join(__dirname, '../../..', pluginSetting.options.file.store.relativePathFileSystem); + const targetDirectory = path.join( + __dirname, + '../../..', + pluginSetting.options.file.store.relativePathFileSystem, + ); createDirectoryIfNotExists(targetDirectory); // Get the files related to the setting and remove them const files = glob.sync(path.join(targetDirectory, `${key}.*`)); @@ -163,24 +190,33 @@ export class WazuhUtilsCtrl { fs.writeFileSync(path.join(targetDirectory, fileNamePath), bufferFile); // Update the setting in the configuration cache - const pluginSettingValue = pluginSetting.options.file.store.resolveStaticURL(fileNamePath); - await updateConfigurationFile.updateConfiguration({ [key]: pluginSettingValue }); + const pluginSettingValue = + pluginSetting.options.file.store.resolveStaticURL(fileNamePath); + await context.wazuh_core.updateConfigurationFile.updateConfiguration({ + [key]: pluginSettingValue, + }); return response.ok({ body: { data: { - requiresRunningHealthCheck: Boolean(pluginSetting.requiresRunningHealthCheck), - requiresReloadingBrowserTab: Boolean(pluginSetting.requiresReloadingBrowserTab), - requiresRestartingPluginPlatform: Boolean(pluginSetting.requiresRestartingPluginPlatform), + requiresRunningHealthCheck: Boolean( + pluginSetting.requiresRunningHealthCheck, + ), + requiresReloadingBrowserTab: Boolean( + pluginSetting.requiresReloadingBrowserTab, + ), + requiresRestartingPluginPlatform: Boolean( + pluginSetting.requiresRestartingPluginPlatform, + ), updatedConfiguration: { - [key]: pluginSettingValue - } - } - } + [key]: pluginSettingValue, + }, + }, + }, }); }, - 3022 - ) + 3022, + ); /** * Delete a file @@ -190,64 +226,96 @@ export class WazuhUtilsCtrl { * @returns {Object} Configuration File or ErrorResponse */ deleteFile = this.routeDecoratorProtectedAdministratorRoleValidToken( - async (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => { + async ( + context: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory, + ) => { const { key } = request.params; const pluginSetting = PLUGIN_SETTINGS[key]; // Get the files related to the setting and remove them - const targetDirectory = path.join(__dirname, '../../..', pluginSetting.options.file.store.relativePathFileSystem); + const targetDirectory = path.join( + __dirname, + '../../..', + pluginSetting.options.file.store.relativePathFileSystem, + ); const files = glob.sync(path.join(targetDirectory, `${key}.*`)); files.forEach(fs.unlinkSync); // Update the setting in the configuration cache const pluginSettingValue = pluginSetting.defaultValue; - await updateConfigurationFile.updateConfiguration({ [key]: pluginSettingValue }); + await context.wazuh_core.updateConfigurationFile.updateConfiguration({ + [key]: pluginSettingValue, + }); return response.ok({ body: { - message: 'All files were removed and the configuration file was updated.', + message: + 'All files were removed and the configuration file was updated.', data: { - requiresRunningHealthCheck: Boolean(pluginSetting.requiresRunningHealthCheck), - requiresReloadingBrowserTab: Boolean(pluginSetting.requiresReloadingBrowserTab), - requiresRestartingPluginPlatform: Boolean(pluginSetting.requiresRestartingPluginPlatform), + requiresRunningHealthCheck: Boolean( + pluginSetting.requiresRunningHealthCheck, + ), + requiresReloadingBrowserTab: Boolean( + pluginSetting.requiresReloadingBrowserTab, + ), + requiresRestartingPluginPlatform: Boolean( + pluginSetting.requiresRestartingPluginPlatform, + ), updatedConfiguration: { - [key]: pluginSettingValue - } - } - } + [key]: pluginSettingValue, + }, + }, + }, }); }, - 3023 - ) + 3023, + ); - private routeDecoratorProtectedAdministratorRoleValidToken(routeHandler, errorCode: number) { + private routeDecoratorProtectedAdministratorRoleValidToken( + routeHandler, + errorCode: number, + ) { return async (context, request, response) => { try { // Check if user has administrator role in token const token = getCookieValueByName(request.headers.cookie, 'wz-token'); if (!token) { return ErrorResponse('No token provided', 401, 401, response); - }; + } const decodedToken = jwtDecode(token); if (!decodedToken) { return ErrorResponse('No permissions in token', 401, 401, response); - }; - if (!decodedToken.rbac_roles || !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID)) { + } + if ( + !decodedToken.rbac_roles || + !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID) + ) { return ErrorResponse('No administrator role', 401, 401, response); - }; + } // Check the provided token is valid - const apiHostID = getCookieValueByName(request.headers.cookie, 'wz-api'); + const apiHostID = getCookieValueByName( + request.headers.cookie, + 'wz-api', + ); if (!apiHostID) { return ErrorResponse('No API id provided', 401, 401, response); - }; - const responseTokenIsWorking = await context.wazuh.api.client.asCurrentUser.request('GET', '/', {}, { apiHostID }); + } + const responseTokenIsWorking = + await context.wazuh.api.client.asCurrentUser.request( + 'GET', + '/', + {}, + { apiHostID }, + ); if (responseTokenIsWorking.status !== 200) { return ErrorResponse('Token is not valid', 401, 401, response); - }; - return await routeHandler(context, request, response) + } + return await routeHandler(context, request, response); } catch (error) { return ErrorResponse(error.message || error, errorCode, 500, response); } - } + }; } } diff --git a/plugins/main/server/lib/api-interceptor.ts b/plugins/main/server/lib/api-interceptor.ts deleted file mode 100644 index 256eaede05..0000000000 --- a/plugins/main/server/lib/api-interceptor.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Wazuh app - Interceptor API entries - * 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 axios, { AxiosResponse } from 'axios'; -import { ManageHosts } from './manage-hosts'; -import https from 'https'; - -const httpsAgent = new https.Agent({ - rejectUnauthorized: false, -}); - -const _axios = axios.create({ httpsAgent }); - -interface APIHost{ - url: string - port: string - username: string - password: string -} - -export interface APIInterceptorRequestOptions{ - apiHostID: string - token: string - forceRefresh?: boolean -} - -export interface APIInterceptorRequestOptionsInternalUser{ - apiHostID: string - forceRefresh?: boolean -} - -const manageHosts = new ManageHosts(); - -// Cache to save the token for the internal user by API host ID -const CacheInternalUserAPIHostToken = new Map(); - -export const authenticate = async (apiHostID: string, authContext?: any): Promise => { - try{ - const api: APIHost = await manageHosts.getHostById(apiHostID); - const optionsRequest = { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - auth: { - username: api.username, - password: api.password, - }, - url: `${api.url}:${api.port}/security/user/authenticate${!!authContext ? '/run_as' : ''}`, - ...(!!authContext ? { data: authContext } : {}) - }; - - const response: AxiosResponse = await _axios(optionsRequest); - const token: string = (((response || {}).data || {}).data || {}).token; - if (!authContext) { - CacheInternalUserAPIHostToken.set(apiHostID, token); - }; - return token; - }catch(error){ - throw error; - } -}; - -const buildRequestOptions = async (method: string, path: string, data: any, { apiHostID, forceRefresh, token }: APIInterceptorRequestOptions) => { - const api = await manageHosts.getHostById(apiHostID); - const { body, params, headers, ...rest } = data; - return { - method: method, - headers: { - 'content-type': 'application/json', - Authorization: 'Bearer ' + token, - ...(headers ? headers : {}) - }, - data: body || rest || {}, - params: params || {}, - url: `${api.url}:${api.port}${path}`, - } -} - -export const requestAsInternalUser = async (method: string, path: string, data: any, options: APIInterceptorRequestOptionsInternalUser) => { - try{ - const token = CacheInternalUserAPIHostToken.has(options.apiHostID) && !options.forceRefresh - ? CacheInternalUserAPIHostToken.get(options.apiHostID) - : await authenticate(options.apiHostID); - return await request(method, path, data, {...options, token}); - }catch(error){ - if (error.response && error.response.status === 401) { - try{ - const token: string = await authenticate(options.apiHostID); - return await request(method, path, data, {...options, token}); - }catch(error){ - throw error; - } - } - throw error; - } -}; - -export const requestAsCurrentUser = async (method: string, path: string, data: any, options: APIInterceptorRequestOptions) => { - return await request(method, path, data, options) -}; - -const request = async (method: string, path: string, data: any, options: any): Promise => { - try{ - const optionsRequest = await buildRequestOptions(method, path, data, options); - const response: AxiosResponse = await _axios(optionsRequest); - return response; - }catch(error){ - throw error; - } -}; diff --git a/plugins/main/server/lib/base-logger.ts b/plugins/main/server/lib/base-logger.ts deleted file mode 100644 index cfc6d4f2b1..0000000000 --- a/plugins/main/server/lib/base-logger.ts +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Wazuh app - Settings controller - * 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 winston from 'winston'; -import fs from 'fs'; -import path from 'path'; -import { getConfiguration } from './get-configuration'; -import { createDataDirectoryIfNotExists, createLogFileIfNotExists } from './filesystem'; - -import { WAZUH_DATA_LOGS_DIRECTORY_PATH, MAX_MB_LOG_FILES } from '../../common/constants'; - -export interface IUIPlainLoggerSettings { - level: string; - message?: string; - data?: any; -} - -export interface IUILoggerSettings extends IUIPlainLoggerSettings { - date: Date; - location: string; -} - -export class BaseLogger { - allowed: boolean = false; - wazuhLogger: winston.Logger | undefined = undefined; - wazuhPlainLogger: winston.Logger | undefined = undefined; - PLAIN_LOGS_PATH: string = ''; - PLAIN_LOGS_FILE_NAME: string = ''; - RAW_LOGS_PATH: string = ''; - RAW_LOGS_FILE_NAME: string = ''; - - constructor(plainLogsFile: string, rawLogsFile: string) { - this.PLAIN_LOGS_PATH = path.join(WAZUH_DATA_LOGS_DIRECTORY_PATH, plainLogsFile); - this.RAW_LOGS_PATH = path.join(WAZUH_DATA_LOGS_DIRECTORY_PATH, rawLogsFile); - this.PLAIN_LOGS_FILE_NAME = plainLogsFile; - this.RAW_LOGS_FILE_NAME = rawLogsFile; - } - - /** - * Initialize loggers, plain and raw logger - */ - private initLogger = () => { - const configurationFile = getConfiguration(); - const level = - typeof (configurationFile || {})['logs.level'] !== 'undefined' && - ['info', 'debug'].includes(configurationFile['logs.level']) - ? configurationFile['logs.level'] - : 'info'; - - // JSON logger - this.wazuhLogger = winston.createLogger({ - level, - format: winston.format.json(), - transports: [ - new winston.transports.File({ - filename: this.RAW_LOGS_PATH, - }), - ], - }); - - // Prevents from exit on error related to the logger. - this.wazuhLogger.exitOnError = false; - - // Plain text logger - this.wazuhPlainLogger = winston.createLogger({ - level, - format: winston.format.simple(), - transports: [ - new winston.transports.File({ - filename: this.PLAIN_LOGS_PATH, - }), - ], - }); - - // Prevents from exit on error related to the logger. - this.wazuhPlainLogger.exitOnError = false; - }; - - /** - * Checks if wazuh/logs exists. If it doesn't exist, it will be created. - */ - initDirectory = async () => { - try { - createDataDirectoryIfNotExists(); - createDataDirectoryIfNotExists('logs'); - if (typeof this.wazuhLogger === 'undefined' || typeof this.wazuhPlainLogger === 'undefined') { - this.initLogger(); - } - this.allowed = true; - return; - } catch (error) { - this.allowed = false; - return Promise.reject(error); - } - }; - - /** - * Returns given file size in MB, if the file doesn't exist returns 0 - * @param {*} filename Path to the file - */ - getFilesizeInMegaBytes = (filename) => { - if (this.allowed) { - if (fs.existsSync(filename)) { - const stats = fs.statSync(filename); - const fileSizeInMegaBytes = stats.size; - - return fileSizeInMegaBytes / 1000000.0; - } - } - return 0; - }; - - /** - * Check if file exist - * @param filename - * @returns boolean - */ - checkFileExist = (filename) => { - return fs.existsSync(filename); - }; - - rotateFiles = (file: string, pathFile: string, log?: string) => { - if (this.getFilesizeInMegaBytes(pathFile) >= MAX_MB_LOG_FILES) { - const fileExtension = path.extname(file); - const fileName = path.basename(file, fileExtension); - fs.renameSync( - pathFile, - `${WAZUH_DATA_LOGS_DIRECTORY_PATH}/${fileName}-${new Date().getTime()}${fileExtension}` - ); - if (log) { - fs.writeFileSync(pathFile, log + '\n'); - } - } - }; - - /** - * Checks if the wazuhapp.log file size is greater than 100MB, if so it rotates the file. - */ - private checkFiles = () => { - createLogFileIfNotExists(this.RAW_LOGS_PATH); - createLogFileIfNotExists(this.PLAIN_LOGS_PATH); - if (this.allowed) { - // check raw log file - this.rotateFiles( - this.RAW_LOGS_FILE_NAME, - this.RAW_LOGS_PATH, - JSON.stringify({ - date: new Date(), - level: 'info', - location: 'logger', - message: 'Rotated log file', - }) - ); - // check log file - this.rotateFiles(this.PLAIN_LOGS_FILE_NAME, this.PLAIN_LOGS_PATH); - } - }; - - /** - * Get Current Date - * @returns string - */ - private yyyymmdd = () => { - const now = new Date(); - const y = now.getFullYear(); - const m = now.getMonth() + 1; - const d = now.getDate(); - const seconds = now.getSeconds(); - const minutes = now.getMinutes(); - const hour = now.getHours(); - return `${y}/${m < 10 ? '0' : ''}${m}/${d < 10 ? '0' : ''}${d} ${hour}:${minutes}:${seconds}`; - }; - - /** - * This function filter some known interfaces to avoid log hug objects - * @param data string | object - * @returns the data parsed - */ - private parseData = (data: any) => { - let parsedData = - data instanceof Error - ? { - message: data.message, - stack: data.stack, - } - : data; - - // when error is AxiosError, it extends from Error - if (data.isAxiosError) { - const { config } = data; - parsedData = { - ...parsedData, - config: { - url: config.url, - method: config.method, - data: config.data, - params: config.params, - }, - }; - } - - if (typeof parsedData === 'object') parsedData.toString = () => JSON.stringify(parsedData); - - return parsedData; - }; - - /** - * Main function to add a new log - * @param {*} location File where the log is being thrown - * @param {*} data Message or object to log - * @param {*} level Optional, default is 'error' - */ - async log(location: string, data: any, level: string) { - const parsedData = this.parseData(data); - return this.initDirectory() - .then(() => { - if (this.allowed) { - this.checkFiles(); - const plainLogData: IUIPlainLoggerSettings = { - level: level || 'error', - message: `${this.yyyymmdd()}: ${location || 'Unknown origin'}: ${ - parsedData.toString() || 'An error occurred' - }`, - }; - - this.wazuhPlainLogger.log(plainLogData); - - const logData: IUILoggerSettings = { - date: new Date(), - level: level || 'error', - location: location || 'Unknown origin', - data: parsedData || 'An error occurred', - }; - - if (typeof data == 'string') { - logData.message = parsedData; - delete logData.data; - } - - this.wazuhLogger.log(logData); - } - }) - .catch((error) => { - console.error(`Cannot create the logs directory due to:\n${error.message || error}`); - throw error; - }); - } -} diff --git a/plugins/main/server/lib/cache-api-user-has-run-as.ts b/plugins/main/server/lib/cache-api-user-has-run-as.ts deleted file mode 100644 index 725ec3771b..0000000000 --- a/plugins/main/server/lib/cache-api-user-has-run-as.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Wazuh app - Service which caches the API user allow run as - * 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 * as ApiInterceptor from './api-interceptor'; -import { ManageHosts } from './manage-hosts'; -import { log } from './logger'; -// Private variable to save the cache -const _cache = {}; - -// Export an interface which interacts with the private cache object -export const CacheInMemoryAPIUserAllowRunAs = { - // Set an entry with API ID, username and allow_run_as - set: (apiID: string, username: string, allow_run_as : number): void => { - if(!_cache[apiID]){ - _cache[apiID] = {}; // Create a API ID entry if it doesn't exist in cache object - }; - _cache[apiID][username] = allow_run_as; - }, - // Get the value of an entry with API ID and username from cache - get: (apiID: string, username: string): number => _cache[apiID] && typeof _cache[apiID][username] !== 'undefined' ? _cache[apiID][username] : API_USER_STATUS_RUN_AS.ALL_DISABLED, - // Check if it exists the API ID and username in the cache - has: (apiID: string, username: string): boolean => _cache[apiID] && typeof _cache[apiID][username] !== 'undefined' ? true : false -}; - -const manageHosts = new ManageHosts(); - -export const APIUserAllowRunAs = { - async check(apiId: string): Promise{ - try{ - const api = await manageHosts.getHostById(apiId); - log('APIUserAllowRunAs:check', `Check if API user ${api.username} (${apiId}) has run_as`, 'debug'); - // Check if api.run_as is false or undefined, then it set to false in cache - if(!api.run_as){ - CacheInMemoryAPIUserAllowRunAs.set(apiId, api.username, API_USER_STATUS_RUN_AS.HOST_DISABLED); - }; - // Check if the API user is cached and returns it - if(CacheInMemoryAPIUserAllowRunAs.has(apiId, api.username)){ - return CacheInMemoryAPIUserAllowRunAs.get(apiId, api.username); - }; - const response = await ApiInterceptor.requestAsInternalUser( - 'get', - '/security/users/me', - {}, - { apiHostID: apiId } - ); - const statusUserAllowRunAs = response.data.data.affected_items[0].allow_run_as ? API_USER_STATUS_RUN_AS.ENABLED : API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED; - - // Cache the run_as for the API user - CacheInMemoryAPIUserAllowRunAs.set(apiId, api.username, statusUserAllowRunAs); - return statusUserAllowRunAs; - }catch(error){ - log('APIUserAllowRunAs:check', error.message || error); - return API_USER_STATUS_RUN_AS.ALL_DISABLED; - } - }, - async canUse(apiId: string): Promise{ - const ApiUserCanUseStatus = await APIUserAllowRunAs.check(apiId); - if(ApiUserCanUseStatus === API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED){ - const api = await manageHosts.getHostById(apiId); - throw new Error(`API with host ID [${apiId}] misconfigured. The Wazuh API user [${api.username}] is not allowed to use [run_as]. Allow it in the user configuration or set [run_as] host setting with [false] value.`); - } - return ApiUserCanUseStatus; - } -}; - -/** - * @example - * HOST = set in wazuh.yml config - * USER = set in user interface - * - * ALL_DISABLED - * binary 00 = decimal 0 ---> USER 0 y HOST 0 - * - * USER_NOT_ALLOWED - * binary 01 = decimal 1 ---> USER 0 y HOST 1 - * - * HOST_DISABLED - * binary 10 = decimal 2 ---> USER 1 y HOST 0 - * - * ENABLED - * binary 11 = decimal 3 ---> USER 1 y HOST 1 - */ -export enum API_USER_STATUS_RUN_AS{ - ALL_DISABLED = 0, // Wazuh HOST and USER API user configured with run_as=false or undefined - USER_NOT_ALLOWED = 1, // Wazuh HOST API user configured with run_as = TRUE in wazuh.yml but it has not run_as in Wazuh API - HOST_DISABLED = 2, // Wazuh HOST API user configured with run_as=false in wazuh.yml but it has not run_as in Wazuh API - ENABLED = 3 // Wazuh API user configured with run_as=true and allow run_as -} diff --git a/plugins/main/server/lib/logger.ts b/plugins/main/server/lib/logger.ts deleted file mode 100644 index c21394e4c4..0000000000 --- a/plugins/main/server/lib/logger.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Wazuh app - Module for logging functions - * 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 { BaseLogger } from './base-logger'; -import { - WAZUH_DATA_LOGS_PLAIN_FILENAME, - WAZUH_DATA_LOGS_RAW_FILENAME, -} from '../../common/constants'; - -const logger = new BaseLogger(WAZUH_DATA_LOGS_PLAIN_FILENAME, WAZUH_DATA_LOGS_RAW_FILENAME); - -export const log = (location, message, level) => { - logger.log(location, message, level); -}; diff --git a/plugins/main/server/lib/manage-hosts.ts b/plugins/main/server/lib/manage-hosts.ts deleted file mode 100644 index 65959623eb..0000000000 --- a/plugins/main/server/lib/manage-hosts.ts +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Wazuh app - Module to update the configuration file - * 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 fs from 'fs'; -import yml from 'js-yaml'; -import { log } from './logger'; -import { UpdateRegistry } from './update-registry'; -import { initialWazuhConfig } from './initial-wazuh-config'; -import { WAZUH_DATA_CONFIG_APP_PATH } from '../../common/constants'; -import { createDataDirectoryIfNotExists } from '../lib/filesystem'; - -export class ManageHosts { - busy: boolean; - file: string; - updateRegistry: UpdateRegistry; - initialConfig: string; - constructor() { - this.busy = false; - this.file = WAZUH_DATA_CONFIG_APP_PATH; - this.updateRegistry = new UpdateRegistry(); - this.initialConfig = initialWazuhConfig; - } - - /** - * Composes the host structure - * @param {Object} host - * @param {String} id - */ - composeHost(host, id) { - try { - log('manage-hosts:composeHost', 'Composing host', 'debug'); - return ` - ${!id ? new Date().getTime() : id}: - url: ${host.url} - port: ${host.port} - username: ${host.username || host.user} - password: ${host.password}`; - } catch (error) { - log('manage-hosts:composeHost', error.message || error); - throw error; - } - } - - /** - * Regex to build the host - * @param {Object} host - */ - composeRegex(host) { - try { - const hostId = Object.keys(host)[0]; - const reg = `\\s*-\\s*${hostId}\\s*:\\s*\\n*\\s*url\\s*:\\s*\\S*\\s*\\n*\\s*port\\s*:\\s*\\S*\\s*\\n*\\s*username\\s*:\\s*\\S*\\s*\\n*\\s*password\\s*:\\s*\\S*`; - log('manage-hosts:composeRegex', 'Composing regex', 'debug'); - return new RegExp(`${reg}`, 'gm'); - } catch (error) { - log('manage-hosts:composeRegex', error.message || error); - throw error; - } - } - - /** - * Returns the hosts in the wazuh.yml - */ - async getHosts() { - try { - this.checkBusy(); - this.busy = true; - createDataDirectoryIfNotExists(); - createDataDirectoryIfNotExists('config'); - if (!fs.existsSync(WAZUH_DATA_CONFIG_APP_PATH)) { - await fs.writeFileSync(this.file, this.initialConfig, { - encoding: 'utf8', - mode: 0o600, - }); - } - const raw = fs.readFileSync(this.file, { encoding: 'utf-8' }); - this.busy = false; - const content = yml.load(raw); - log('manage-hosts:getHosts', 'Getting hosts', 'debug'); - const entries = (content || {})['hosts'] || []; - return entries; - } catch (error) { - this.busy = false; - log('manage-hosts:getHosts', error.message || error); - return Promise.reject(error); - } - } - - /** - * This function checks if the hosts: key exists in the wazuh.yml for preventing duplicate in case of there's not any host defined - */ - async checkIfHostsKeyExists() { - try { - log('manage-hosts:checkIfHostsKeyExists', 'Checking hosts key', 'debug'); - this.busy = true; - const raw = fs.readFileSync(this.file, { encoding: 'utf-8' }); - this.busy = false; - const content = yml.load(raw); - return Object.keys(content || {}).includes('hosts'); - } catch (error) { - log('manage-hosts:checkIfHostsKeyExists', error.message || error); - this.busy = false; - return Promise.reject(error); - } - } - - /** - * Returns the IDs of the current hosts in the wazuh.yml - */ - async getCurrentHostsIds() { - try { - const hosts = await this.getHosts(); - const ids = hosts.map(h => { - return Object.keys(h)[0]; - }); - log('manage-hosts:getCurrentHostsIds', 'Getting hosts ids', 'debug'); - return ids; - } catch (error) { - log('manage-hosts:getCurrentHostsIds', error.message || error); - return Promise.reject(error); - } - } - - /** - * Get host by id - * @param {String} id - */ - async getHostById(id) { - try { - log('manage-hosts:getHostById', `Getting host ${id}`, 'debug'); - const hosts = await this.getHosts(); - const host = hosts.filter(h => { - return Object.keys(h)[0] == id; - }); - if (host && !host.length) { - throw new Error('Selected API is no longer available in wazuh.yml'); - } - const key = Object.keys(host[0])[0]; - const result = Object.assign(host[0][key], { id: key }) || {}; - return result; - } catch (error) { - log('manage-hosts:getHostById', error.message || error); - return Promise.reject(error); - } - } - - /** - * Decodes the API password - * @param {String} password - */ - decodeApiPassword(password) { - return Buffer.from(password, 'base64').toString('ascii'); - } - - /** - * Iterate the array with the API entries in given from the .wazuh index in order to create a valid array - * @param {Object} apiEntries - */ - transformIndexedApis(apiEntries) { - const entries = []; - try { - apiEntries.map(entry => { - const id = entry._id; - const host = entry._source; - const api = { - id: id, - url: host.url, - port: host.api_port, - username: host.api_username, - password: this.decodeApiPassword(host.api_password), - cluster_info: host.cluster_info, - }; - entries.push(api); - }); - log( - 'manage-hosts:transformIndexedApis', - 'Transforming index API schedule to wazuh.yml', - 'debug', - ); - } catch (error) { - log('manage-hosts:transformIndexedApis', error.message || error); - throw error; - } - return entries; - } - - /** - * Calls transformIndexedApis() to get the entries to migrate and after that calls addSeveralHosts() - * @param {Object} apiEntries - */ - async migrateFromIndex(apiEntries) { - try { - const apis = this.transformIndexedApis(apiEntries); - return await this.addSeveralHosts(apis); - } catch (error) { - log('manage-hosts:migrateFromIndex', error.message || error); - return Promise.reject(error); - } - } - - /** - * Receives an array of hosts and checks if any host is already in the wazuh.yml, in this case is removed from the received array and returns the resulting array - * @param {Array} hosts - */ - async cleanExistingHosts(hosts) { - try { - const currentHosts = await this.getCurrentHostsIds(); - const cleanHosts = hosts.filter(h => { - return !currentHosts.includes(h.id); - }); - log( - 'manage-hosts:cleanExistingHosts', - 'Preventing add existings hosts', - 'debug', - ); - return cleanHosts; - } catch (error) { - log('manage-hosts:cleanExistingHosts', error.message || error); - return Promise.reject(error); - } - } - - /** - * Throws an error is the wazuh.yml is busy - */ - checkBusy() { - if (this.busy) - throw new Error('Another process is writting the configuration file'); - } - - /** - * Recursive function used to add several APIs entries - * @param {Array} hosts - */ - async addSeveralHosts(hosts) { - try { - log('manage-hosts:addSeveralHosts', 'Adding several', 'debug'); - const hostsToAdd = await this.cleanExistingHosts(hosts); - if (!hostsToAdd.length) return 'There are not APIs entries to migrate'; - for (let idx in hostsToAdd) { - const entry = hostsToAdd[idx]; - await this.addHost(entry); - } - return 'All APIs entries were migrated to the wazuh.yml'; - } catch (error) { - log('manage-hosts:addSeveralHosts', error.message || error); - return Promise.reject(error); - } - } - - /** - * Add a single host - * @param {Obeject} host - */ - async addHost(host) { - const id = host.id || new Date().getTime(); - const compose = this.composeHost(host, id); - let data = await fs.readFileSync(this.file, { encoding: 'utf-8' }); - try { - this.checkBusy(); - const hosts = (await this.getHosts()) || []; - this.busy = true; - if (!hosts.length) { - const hostsExists = await this.checkIfHostsKeyExists(); - const result = !hostsExists - ? `${data}\nhosts:\n${compose}\n` - : `${data}\n${compose}\n`; - await fs.writeFileSync(this.file, result, 'utf8'); - } else { - const lastHost = (hosts || []).pop(); - if (lastHost) { - const lastHostObject = this.composeHost( - lastHost[Object.keys(lastHost)[0]], - Object.keys(lastHost)[0], - ); - const regex = this.composeRegex(lastHost); - const replace = data.replace( - regex, - `\n${lastHostObject}\n${compose}\n`, - ); - await fs.writeFileSync(this.file, replace, 'utf8'); - } - } - this.busy = false; - this.updateRegistry.migrateToRegistry( - id, - host.cluster_info, - ); - log('manage-hosts:addHost', `Host ${id} was properly added`, 'debug'); - return id; - } catch (error) { - this.busy = false; - log('manage-hosts:addHost', error.message || error); - return Promise.reject(error); - } - } - - /** - * Delete a host from the wazuh.yml - * @param {Object} req - */ - async deleteHost(req) { - let data = await fs.readFileSync(this.file, { encoding: 'utf-8' }); - try { - this.checkBusy(); - const hosts = (await this.getHosts()) || []; - this.busy = true; - if (!hosts.length) { - throw new Error('There are not configured hosts.'); - } else { - const hostsNumber = hosts.length; - const target = (hosts || []).find(element => { - return Object.keys(element)[0] === req.params.id; - }); - if (!target) { - throw new Error(`Host ${req.params.id} not found.`); - } - const regex = this.composeRegex(target); - const result = data.replace(regex, ``); - await fs.writeFileSync(this.file, result, 'utf8'); - if (hostsNumber === 1) { - data = await fs.readFileSync(this.file, { encoding: 'utf-8' }); - const clearHosts = data.replace( - new RegExp(`hosts:\\s*[\\n\\r]`, 'gm'), - '', - ); - await fs.writeFileSync(this.file, clearHosts, 'utf8'); - } - } - this.busy = false; - log( - 'manage-hosts:deleteHost', - `Host ${req.params.id} was properly deleted`, - 'debug', - ); - return true; - } catch (error) { - this.busy = false; - log('manage-hosts:deleteHost', error.message || error); - return Promise.reject(error); - } - } - - /** - * Updates the hosts information - * @param {String} id - * @param {Object} host - */ - async updateHost(id, host) { - let data = await fs.readFileSync(this.file, { encoding: 'utf-8' }); - try { - this.checkBusy(); - const hosts = (await this.getHosts()) || []; - this.busy = true; - if (!hosts.length) { - throw new Error('There are not configured hosts.'); - } else { - const target = (hosts || []).find(element => { - return Object.keys(element)[0] === id; - }); - if (!target) { - throw new Error(`Host ${id} not found.`); - } - const regex = this.composeRegex(target); - const result = data.replace(regex, `\n${this.composeHost(host, id)}`); - await fs.writeFileSync(this.file, result, 'utf8'); - } - this.busy = false; - log( - 'manage-hosts:updateHost', - `Host ${id} was properly updated`, - 'debug', - ); - return true; - } catch (error) { - this.busy = false; - log('manage-hosts:updateHost', error.message || error); - return Promise.reject(error); - } - } -} diff --git a/plugins/main/server/lib/parse-cron.ts b/plugins/main/server/lib/parse-cron.ts index 5330e065d2..45927118e7 100644 --- a/plugins/main/server/lib/parse-cron.ts +++ b/plugins/main/server/lib/parse-cron.ts @@ -9,7 +9,6 @@ * * Find more information about this on the LICENSE file. */ -import { log } from './logger'; import cron from 'node-cron'; import { WAZUH_MONITORING_DEFAULT_CRON_FREQ } from '../../common/constants'; @@ -17,34 +16,30 @@ export function parseCron(interval: string) { try { if (!interval) throw new Error('Interval not found'); - const intervalToNumber = parseInt(interval); + const intervalToNumber: number = parseInt(interval); - if (!intervalToNumber || typeof intervalToNumber !== 'number'){ + if (!intervalToNumber || typeof intervalToNumber !== 'number') { throw new Error('Interval not valid'); - }; - if (intervalToNumber < 60){ // 60 seconds / 1 minute + } + if (intervalToNumber < 60) { + // 60 seconds / 1 minute throw new Error('Interval too low'); - }; - if (intervalToNumber >= 84600){ + } + if (intervalToNumber >= 84600) { throw new Error('Interval too high'); - } + } const minutes = parseInt(intervalToNumber / 60); const cronstr = `0 */${minutes} * * * *`; - if (!cron.validate(cronstr)){ + if (!cron.validate(cronstr)) { throw new Error( - 'Generated cron expression not valid for node-cron module' + 'Generated cron expression not valid for node-cron module', ); } - log('cron:parse-interval', `Using the next interval: ${cronstr}`, 'debug'); return cronstr; } catch (error) { - log( - 'cron:parse-interval', - `Using default value ${WAZUH_MONITORING_DEFAULT_CRON_FREQ} due to: ${error.message || error}` - ); return WAZUH_MONITORING_DEFAULT_CRON_FREQ; } } diff --git a/plugins/main/server/lib/reporting/extended-information.ts b/plugins/main/server/lib/reporting/extended-information.ts index 377ba9408c..ffb0b8f89d 100644 --- a/plugins/main/server/lib/reporting/extended-information.ts +++ b/plugins/main/server/lib/reporting/extended-information.ts @@ -1,4 +1,3 @@ -import { log } from '../logger'; import SummaryTable from './summary-table'; import summaryTablesDefinitions from './summary-tables-definitions'; import * as VulnerabilityRequest from './vulnerability-request'; @@ -16,33 +15,41 @@ import { ReportPrinter } from './printer'; import moment from 'moment'; import { getSettingDefaultValue } from '../../../common/services/settings'; - - - /** - * This build the agents table - * @param {Array} ids ids of agents - * @param {String} apiId API id - */ -export async function buildAgentsTable(context, printer: ReportPrinter, agentIDs: string[], apiId: string, groupID: string = '') { + * This build the agents table + * @param {Array} ids ids of agents + * @param {String} apiId API id + */ +export async function buildAgentsTable( + context, + printer: ReportPrinter, + agentIDs: string[], + apiId: string, + groupID: string = '', +) { const dateFormat = await context.core.uiSettings.client.get('dateFormat'); if ((!agentIDs || !agentIDs.length) && !groupID) return; - log('reporting:buildAgentsTable', `${agentIDs.length} agents for API ${apiId}`, 'info'); + printer.logger.debug(`${agentIDs.length} agents for API ${apiId}`); try { let agentsData = []; if (groupID) { let totalAgentsInGroup = null; do { - const { data: { data: { affected_items, total_affected_items } } } = await context.wazuh.api.client.asCurrentUser.request( + const { + data: { + data: { affected_items, total_affected_items }, + }, + } = await context.wazuh.api.client.asCurrentUser.request( 'GET', `/groups/${groupID}/agents`, { params: { offset: agentsData.length, - select: 'dateAdd,id,ip,lastKeepAlive,manager,name,os.name,os.version,version', - } + select: + 'dateAdd,id,ip,lastKeepAlive,manager,name,os.name,os.version,version', + }, }, - { apiHostID: apiId } + { apiHostID: apiId }, ); !totalAgentsInGroup && (totalAgentsInGroup = total_affected_items); agentsData = [...agentsData, ...affected_items]; @@ -50,24 +57,27 @@ export async function buildAgentsTable(context, printer: ReportPrinter, agentIDs } else { for (const agentID of agentIDs) { try { - const { data: { data: { affected_items: [agent] } } } = await context.wazuh.api.client.asCurrentUser.request( + const { + data: { + data: { + affected_items: [agent], + }, + }, + } = await context.wazuh.api.client.asCurrentUser.request( 'GET', `/agents`, { params: { q: `id=${agentID}`, - select: 'dateAdd,id,ip,lastKeepAlive,manager,name,os.name,os.version,version', - } + select: + 'dateAdd,id,ip,lastKeepAlive,manager,name,os.name,os.version,version', + }, }, - { apiHostID: apiId } + { apiHostID: apiId }, ); agentsData.push(agent); } catch (error) { - log( - 'reporting:buildAgentsTable', - `Skip agent due to: ${error.message || error}`, - 'debug' - ); + printer.logger.debug(`Skip agent due to: ${error.message || error}`); } } } @@ -87,13 +97,16 @@ export async function buildAgentsTable(context, printer: ReportPrinter, agentIDs ], items: agentsData .filter(agent => agent) // Remove undefined agents when Wazuh API no longer finds and agentID - .map((agent) => { + .map(agent => { return { ...agent, - os: (agent.os && agent.os.name && agent.os.version) ? `${agent.os.name} ${agent.os.version}` : '', + os: + agent.os && agent.os.name && agent.os.version + ? `${agent.os.name} ${agent.os.version}` + : '', lastKeepAlive: moment(agent.lastKeepAlive).format(dateFormat), - dateAdd: moment(agent.dateAdd).format(dateFormat) - } + dateAdd: moment(agent.dateAdd).format(dateFormat), + }; }), }); } else if (!agentsData.length && groupID) { @@ -103,9 +116,8 @@ export async function buildAgentsTable(context, printer: ReportPrinter, agentIDs style: { fontSize: 12, color: '#000' }, }); } - } catch (error) { - log('reporting:buildAgentsTable', error.message || error); + printer.logger.error(error.message || error); return Promise.reject(error); } } @@ -138,36 +150,34 @@ export async function extendedInformation( agent = null, ) { try { - log( - 'reporting:extendedInformation', - `Section ${section} and tab ${tab}, API is ${apiId}. From ${from} to ${to}. Filters ${JSON.stringify(filters)}. Index pattern ${pattern}`, - 'info' + printer.logger.debug( + `Section ${section} and tab ${tab}, API is ${apiId}. From ${from} to ${to}. Filters ${JSON.stringify( + filters, + )}. Index pattern ${pattern}`, ); if (section === 'agents' && !agent) { - throw new Error('Reporting for specific agent needs an agent ID in order to work properly'); + throw new Error( + 'Reporting for specific agent needs an agent ID in order to work properly', + ); } const agents = await context.wazuh.api.client.asCurrentUser.request( 'GET', '/agents', { params: { limit: 1 } }, - { apiHostID: apiId } + { apiHostID: apiId }, ); const totalAgents = agents.data.data.total_affected_items; //--- OVERVIEW - VULS if (section === 'overview' && tab === 'vuls') { - log( - 'reporting:extendedInformation', - 'Fetching overview vulnerability detector metrics', - 'debug' - ); + printer.logger.debug('Fetching overview vulnerability detector metrics'); const vulnerabilitiesLevels = ['Low', 'Medium', 'High', 'Critical']; const vulnerabilitiesResponsesCount = ( await Promise.all( - vulnerabilitiesLevels.map(async (vulnerabilitiesLevel) => { + vulnerabilitiesLevels.map(async vulnerabilitiesLevel => { try { const count = await VulnerabilityRequest.uniqueSeverityCount( context, @@ -176,25 +186,23 @@ export async function extendedInformation( vulnerabilitiesLevel, filters, allowedAgentsFilter, - pattern + pattern, ); return count ? `${count} of ${totalAgents} agents have ${vulnerabilitiesLevel.toLocaleLowerCase()} vulnerabilities.` : undefined; - } catch (error) { } - }) + } catch (error) {} + }), ) - ).filter((vulnerabilitiesResponse) => vulnerabilitiesResponse); + ).filter(vulnerabilitiesResponse => vulnerabilitiesResponse); printer.addList({ title: { text: 'Summary', style: 'h2' }, list: vulnerabilitiesResponsesCount, }); - log( - 'reporting:extendedInformation', + printer.logger.debug( 'Fetching overview vulnerability detector top 3 agents by category', - 'debug' ); const lowRank = await VulnerabilityRequest.topAgentCount( context, @@ -203,7 +211,7 @@ export async function extendedInformation( 'Low', filters, allowedAgentsFilter, - pattern + pattern, ); const mediumRank = await VulnerabilityRequest.topAgentCount( context, @@ -212,7 +220,7 @@ export async function extendedInformation( 'Medium', filters, allowedAgentsFilter, - pattern + pattern, ); const highRank = await VulnerabilityRequest.topAgentCount( context, @@ -221,7 +229,7 @@ export async function extendedInformation( 'High', filters, allowedAgentsFilter, - pattern + pattern, ); const criticalRank = await VulnerabilityRequest.topAgentCount( context, @@ -230,12 +238,10 @@ export async function extendedInformation( 'Critical', filters, allowedAgentsFilter, - pattern + pattern, ); - log( - 'reporting:extendedInformation', + printer.logger.debug( 'Adding overview vulnerability detector top 3 agents by category', - 'debug' ); if (criticalRank && criticalRank.length) { printer.addContentWithNewLine({ @@ -273,17 +279,18 @@ export async function extendedInformation( printer.addNewLine(); } - log( - 'reporting:extendedInformation', + printer.logger.debug( 'Fetching overview vulnerability detector top 3 CVEs', - 'debug' ); - const cveRank = await VulnerabilityRequest.topCVECount(context, from, to, filters, allowedAgentsFilter, pattern); - log( - 'reporting:extendedInformation', - 'Adding overview vulnerability detector top 3 CVEs', - 'debug' + const cveRank = await VulnerabilityRequest.topCVECount( + context, + from, + to, + filters, + allowedAgentsFilter, + pattern, ); + printer.logger.debug('Adding overview vulnerability detector top 3 CVEs'); if (cveRank && cveRank.length) { printer.addSimpleTable({ title: { text: 'Top 3 CVE', style: 'h2' }, @@ -291,18 +298,28 @@ export async function extendedInformation( { id: 'top', label: 'Top' }, { id: 'cve', label: 'CVE' }, ], - items: cveRank.map((item) => ({ top: cveRank.indexOf(item) + 1, cve: item })), + items: cveRank.map(item => ({ + top: cveRank.indexOf(item) + 1, + cve: item, + })), }); } } //--- OVERVIEW - GENERAL if (section === 'overview' && tab === 'general') { - log('reporting:extendedInformation', 'Fetching top 3 agents with level 15 alerts', 'debug'); + printer.logger.debug('Fetching top 3 agents with level 15 alerts'); - const level15Rank = await OverviewRequest.topLevel15(context, from, to, filters, allowedAgentsFilter, pattern); + const level15Rank = await OverviewRequest.topLevel15( + context, + from, + to, + filters, + allowedAgentsFilter, + pattern, + ); - log('reporting:extendedInformation', 'Adding top 3 agents with level 15 alerts', 'debug'); + printer.logger.debug('Adding top 3 agents with level 15 alerts'); if (level15Rank.length) { printer.addContent({ text: 'Top 3 agents with level 15 alerts', @@ -314,16 +331,16 @@ export async function extendedInformation( //--- OVERVIEW - PM if (section === 'overview' && tab === 'pm') { - log('reporting:extendedInformation', 'Fetching most common rootkits', 'debug'); + printer.logger.debug('Fetching most common rootkits'); const top5RootkitsRank = await RootcheckRequest.top5RootkitsDetected( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); - log('reporting:extendedInformation', 'Adding most common rootkits', 'debug'); + printer.logger.debug('Adding most common rootkits'); if (top5RootkitsRank && top5RootkitsRank.length) { printer .addContentWithNewLine({ @@ -331,12 +348,11 @@ export async function extendedInformation( style: 'h2', }) .addContentWithNewLine({ - text: - 'Rootkits are a set of software tools that enable an unauthorized user to gain control of a computer system without being detected.', + text: 'Rootkits are a set of software tools that enable an unauthorized user to gain control of a computer system without being detected.', style: 'standard', }) .addSimpleTable({ - items: top5RootkitsRank.map((item) => { + items: top5RootkitsRank.map(item => { return { top: top5RootkitsRank.indexOf(item) + 1, name: item }; }), columns: [ @@ -345,14 +361,14 @@ export async function extendedInformation( ], }); } - log('reporting:extendedInformation', 'Fetching hidden pids', 'debug'); + printer.logger.debug('Fetching hidden pids'); const hiddenPids = await RootcheckRequest.agentsWithHiddenPids( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); hiddenPids && printer.addContent({ @@ -371,7 +387,7 @@ export async function extendedInformation( to, filters, allowedAgentsFilter, - pattern + pattern, ); hiddenPorts && printer.addContent({ @@ -388,14 +404,14 @@ export async function extendedInformation( //--- OVERVIEW/AGENTS - PCI if (['overview', 'agents'].includes(section) && tab === 'pci') { - log('reporting:extendedInformation', 'Fetching top PCI DSS requirements', 'debug'); + printer.logger.debug('Fetching top PCI DSS requirements'); const topPciRequirements = await PCIRequest.topPCIRequirements( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); printer.addContentWithNewLine({ text: 'Most common PCI DSS requirements alerts found', @@ -409,13 +425,18 @@ export async function extendedInformation( filters, allowedAgentsFilter, item, - pattern + pattern, ); - printer.addContentWithNewLine({ text: `Requirement ${item}`, style: 'h3' }); + printer.addContentWithNewLine({ + text: `Requirement ${item}`, + style: 'h3', + }); if (PCI[item]) { const content = - typeof PCI[item] === 'string' ? { text: PCI[item], style: 'standard' } : PCI[item]; + typeof PCI[item] === 'string' + ? { text: PCI[item], style: 'standard' } + : PCI[item]; printer.addContentWithNewLine(content); } @@ -434,14 +455,14 @@ export async function extendedInformation( //--- OVERVIEW/AGENTS - TSC if (['overview', 'agents'].includes(section) && tab === 'tsc') { - log('reporting:extendedInformation', 'Fetching top TSC requirements', 'debug'); + printer.logger.debug('Fetching top TSC requirements'); const topTSCRequirements = await TSCRequest.topTSCRequirements( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); printer.addContentWithNewLine({ text: 'Most common TSC requirements alerts found', @@ -455,13 +476,18 @@ export async function extendedInformation( filters, allowedAgentsFilter, item, - pattern + pattern, ); - printer.addContentWithNewLine({ text: `Requirement ${item}`, style: 'h3' }); + printer.addContentWithNewLine({ + text: `Requirement ${item}`, + style: 'h3', + }); if (TSC[item]) { const content = - typeof TSC[item] === 'string' ? { text: TSC[item], style: 'standard' } : TSC[item]; + typeof TSC[item] === 'string' + ? { text: TSC[item], style: 'standard' } + : TSC[item]; printer.addContentWithNewLine(content); } @@ -480,14 +506,14 @@ export async function extendedInformation( //--- OVERVIEW/AGENTS - GDPR if (['overview', 'agents'].includes(section) && tab === 'gdpr') { - log('reporting:extendedInformation', 'Fetching top GDPR requirements', 'debug'); + printer.logger.debug('Fetching top GDPR requirements'); const topGdprRequirements = await GDPRRequest.topGDPRRequirements( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); printer.addContentWithNewLine({ text: 'Most common GDPR requirements alerts found', @@ -501,13 +527,18 @@ export async function extendedInformation( filters, allowedAgentsFilter, item, - pattern + pattern, ); - printer.addContentWithNewLine({ text: `Requirement ${item}`, style: 'h3' }); + printer.addContentWithNewLine({ + text: `Requirement ${item}`, + style: 'h3', + }); if (GDPR && GDPR[item]) { const content = - typeof GDPR[item] === 'string' ? { text: GDPR[item], style: 'standard' } : GDPR[item]; + typeof GDPR[item] === 'string' + ? { text: GDPR[item], style: 'standard' } + : GDPR[item]; printer.addContentWithNewLine(content); } @@ -527,19 +558,18 @@ export async function extendedInformation( //--- OVERVIEW - AUDIT if (section === 'overview' && tab === 'audit') { - log( - 'reporting:extendedInformation', + printer.logger.debug( 'Fetching agents with high number of failed sudo commands', - 'debug' - ); - const auditAgentsNonSuccess = await AuditRequest.getTop3AgentsSudoNonSuccessful( - context, - from, - to, - filters, - allowedAgentsFilter, - pattern ); + const auditAgentsNonSuccess = + await AuditRequest.getTop3AgentsSudoNonSuccessful( + context, + from, + to, + filters, + allowedAgentsFilter, + pattern, + ); if (auditAgentsNonSuccess && auditAgentsNonSuccess.length) { printer.addContent({ text: 'Agents with high number of failed sudo commands', @@ -547,14 +577,15 @@ export async function extendedInformation( }); await buildAgentsTable(context, printer, auditAgentsNonSuccess, apiId); } - const auditAgentsFailedSyscall = await AuditRequest.getTop3AgentsFailedSyscalls( - context, - from, - to, - filters, - allowedAgentsFilter, - pattern - ); + const auditAgentsFailedSyscall = + await AuditRequest.getTop3AgentsFailedSyscalls( + context, + from, + to, + filters, + allowedAgentsFilter, + pattern, + ); if (auditAgentsFailedSyscall && auditAgentsFailedSyscall.length) { printer.addSimpleTable({ columns: [ @@ -562,7 +593,7 @@ export async function extendedInformation( { id: 'syscall_id', label: 'Syscall ID' }, { id: 'syscall_syscall', label: 'Syscall' }, ], - items: auditAgentsFailedSyscall.map((item) => ({ + items: auditAgentsFailedSyscall.map(item => ({ agent: item.agent, syscall_id: item.syscall.id, syscall_syscall: item.syscall.syscall, @@ -577,25 +608,41 @@ export async function extendedInformation( //--- OVERVIEW - FIM if (section === 'overview' && tab === 'fim') { - log('reporting:extendedInformation', 'Fetching top 3 rules for FIM', 'debug'); - const rules = await SyscheckRequest.top3Rules(context, from, to, filters, allowedAgentsFilter, pattern); + printer.logger.debug('Fetching top 3 rules for FIM'); + const rules = await SyscheckRequest.top3Rules( + context, + from, + to, + filters, + allowedAgentsFilter, + pattern, + ); if (rules && rules.length) { - printer.addContentWithNewLine({ text: 'Top 3 FIM rules', style: 'h2' }).addSimpleTable({ - columns: [ - { id: 'ruleID', label: 'Rule ID' }, - { id: 'ruleDescription', label: 'Description' }, - ], - items: rules, - title: { - text: 'Top 3 rules that are generating most alerts.', - style: 'standard', - }, - }); + printer + .addContentWithNewLine({ text: 'Top 3 FIM rules', style: 'h2' }) + .addSimpleTable({ + columns: [ + { id: 'ruleID', label: 'Rule ID' }, + { id: 'ruleDescription', label: 'Description' }, + ], + items: rules, + title: { + text: 'Top 3 rules that are generating most alerts.', + style: 'standard', + }, + }); } - log('reporting:extendedInformation', 'Fetching top 3 agents for FIM', 'debug'); - const agents = await SyscheckRequest.top3agents(context, from, to, filters, allowedAgentsFilter, pattern); + printer.logger.debug('Fetching top 3 agents for FIM'); + const agents = await SyscheckRequest.top3agents( + context, + from, + to, + filters, + allowedAgentsFilter, + pattern, + ); if (agents && agents.length) { printer.addContentWithNewLine({ @@ -603,8 +650,7 @@ export async function extendedInformation( style: 'h2', }); printer.addContentWithNewLine({ - text: - 'Top 3 agents that have most FIM alerts from level 7 to level 15. Take care about them.', + text: 'Top 3 agents that have most FIM alerts from level 7 to level 15. Take care about them.', style: 'standard', }); await buildAgentsTable(context, printer, agents, apiId); @@ -613,14 +659,14 @@ export async function extendedInformation( //--- AGENTS - AUDIT if (section === 'agents' && tab === 'audit') { - log('reporting:extendedInformation', `Fetching most common failed syscalls`, 'debug'); + printer.logger.debug('Fetching most common failed syscalls'); const auditFailedSyscall = await AuditRequest.getTopFailedSyscalls( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); auditFailedSyscall && auditFailedSyscall.length && @@ -636,18 +682,15 @@ export async function extendedInformation( //--- AGENTS - FIM if (section === 'agents' && tab === 'fim') { - log( - 'reporting:extendedInformation', - `Fetching syscheck database for agent ${agent}`, - 'debug' - ); + printer.logger.debug(`Fetching syscheck database for agent ${agent}`); - const lastScanResponse = await context.wazuh.api.client.asCurrentUser.request( - 'GET', - `/syscheck/${agent}/last_scan`, - {}, - { apiHostID: apiId } - ); + const lastScanResponse = + await context.wazuh.api.client.asCurrentUser.request( + 'GET', + `/syscheck/${agent}/last_scan`, + {}, + { apiHostID: apiId }, + ); if (lastScanResponse && lastScanResponse.data) { const lastScanData = lastScanResponse.data.data.affected_items[0]; @@ -667,14 +710,14 @@ export async function extendedInformation( printer.addNewLine(); } - log('reporting:extendedInformation', `Fetching last 10 deleted files for FIM`, 'debug'); + printer.logger.debug('Fetching last 10 deleted files for FIM'); const lastTenDeleted = await SyscheckRequest.lastTenDeletedFiles( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); lastTenDeleted && @@ -688,14 +731,14 @@ export async function extendedInformation( title: 'Last 10 deleted files', }); - log('reporting:extendedInformation', `Fetching last 10 modified files`, 'debug'); + printer.logger.debug('Fetching last 10 modified files'); const lastTenModified = await SyscheckRequest.lastTenModifiedFiles( context, from, to, filters, allowedAgentsFilter, - pattern + pattern, ); lastTenModified && @@ -712,11 +755,7 @@ export async function extendedInformation( //--- AGENTS - SYSCOLLECTOR if (section === 'agents' && tab === 'syscollector') { - log( - 'reporting:extendedInformation', - `Fetching hardware information for agent ${agent}`, - 'debug' - ); + printer.logger.debug(`Fetching hardware information for agent ${agent}`); const requestsSyscollectorLists = [ { endpoint: `/syscollector/${agent}/hardware`, @@ -724,12 +763,12 @@ export async function extendedInformation( list: { title: { text: 'Hardware information', style: 'h2' }, }, - mapResponse: (hardware) => [ + mapResponse: hardware => [ hardware.cpu && hardware.cpu.cores && `${hardware.cpu.cores} cores`, hardware.cpu && hardware.cpu.name, hardware.ram && - hardware.ram.total && - `${Number(hardware.ram.total / 1024 / 1024).toFixed(2)}GB RAM`, + hardware.ram.total && + `${Number(hardware.ram.total / 1024 / 1024).toFixed(2)}GB RAM`, ], }, { @@ -738,29 +777,30 @@ export async function extendedInformation( list: { title: { text: 'Operating system information', style: 'h2' }, }, - mapResponse: (osData) => [ + mapResponse: osData => [ osData.sysname, osData.version, osData.architecture, osData.release, osData.os && - osData.os.name && - osData.os.version && - `${osData.os.name} ${osData.os.version}`, + osData.os.name && + osData.os.version && + `${osData.os.name} ${osData.os.version}`, ], }, ]; const syscollectorLists = await Promise.all( - requestsSyscollectorLists.map(async (requestSyscollector) => { + requestsSyscollectorLists.map(async requestSyscollector => { try { - log('reporting:extendedInformation', requestSyscollector.loggerMessage, 'debug'); - const responseSyscollector = await context.wazuh.api.client.asCurrentUser.request( - 'GET', - requestSyscollector.endpoint, - {}, - { apiHostID: apiId } - ); + printer.logger.debug(requestSyscollector.loggerMessage); + const responseSyscollector = + await context.wazuh.api.client.asCurrentUser.request( + 'GET', + requestSyscollector.endpoint, + {}, + { apiHostID: apiId }, + ); const [data] = (responseSyscollector && responseSyscollector.data && @@ -774,27 +814,25 @@ export async function extendedInformation( }; } } catch (error) { - log('reporting:extendedInformation', error.message || error); + printer.logger.error(error.message || error); } - }) + }), ); if (syscollectorLists) { syscollectorLists - .filter((syscollectorList) => syscollectorList) - .forEach((syscollectorList) => printer.addList(syscollectorList)); + .filter(syscollectorList => syscollectorList) + .forEach(syscollectorList => printer.addList(syscollectorList)); } const vulnerabilitiesRequests = ['Critical', 'High']; const vulnerabilitiesResponsesItems = ( await Promise.all( - vulnerabilitiesRequests.map(async (vulnerabilitiesLevel) => { + vulnerabilitiesRequests.map(async vulnerabilitiesLevel => { try { - log( - 'reporting:extendedInformation', + printer.logger.debug( `Fetching top ${vulnerabilitiesLevel} packages`, - 'debug' ); return await VulnerabilityRequest.topPackages( @@ -804,20 +842,26 @@ export async function extendedInformation( vulnerabilitiesLevel, filters, allowedAgentsFilter, - pattern + pattern, ); } catch (error) { - log('reporting:extendedInformation', error.message || error); + printer.logger.error(error.message || error); } - }) + }), ) ) - .filter((vulnerabilitiesResponse) => vulnerabilitiesResponse) + .filter(vulnerabilitiesResponse => vulnerabilitiesResponse) .flat(); - if (vulnerabilitiesResponsesItems && vulnerabilitiesResponsesItems.length) { + if ( + vulnerabilitiesResponsesItems && + vulnerabilitiesResponsesItems.length + ) { printer.addSimpleTable({ - title: { text: 'Vulnerable packages found (last 24 hours)', style: 'h2' }, + title: { + text: 'Vulnerable packages found (last 24 hours)', + style: 'h2', + }, columns: [ { id: 'package', label: 'Package' }, { id: 'severity', label: 'Severity' }, @@ -836,20 +880,22 @@ export async function extendedInformation( 'Critical', filters, allowedAgentsFilter, - pattern + pattern, ); if (topCriticalPackages && topCriticalPackages.length) { - printer.addContentWithNewLine({ text: 'Critical severity', style: 'h2' }); printer.addContentWithNewLine({ - text: - 'These vulnerabilties are critical, please review your agent. Click on each link to read more about each found vulnerability.', + text: 'Critical severity', + style: 'h2', + }); + printer.addContentWithNewLine({ + text: 'These vulnerabilties are critical, please review your agent. Click on each link to read more about each found vulnerability.', style: 'standard', }); const customul = []; for (const critical of topCriticalPackages) { customul.push({ text: critical.package, style: 'standard' }); customul.push({ - ul: critical.references.map((item) => ({ + ul: critical.references.map(item => ({ text: item.substring(0, 80) + '...', link: item, color: '#1EA5C8', @@ -866,7 +912,7 @@ export async function extendedInformation( 'High', filters, allowedAgentsFilter, - pattern + pattern, ); if (topHighPackages && topHighPackages.length) { printer.addContentWithNewLine({ text: 'High severity', style: 'h2' }); @@ -878,7 +924,7 @@ export async function extendedInformation( for (const critical of topHighPackages) { customul.push({ text: critical.package, style: 'standard' }); customul.push({ - ul: critical.references.map((item) => ({ + ul: critical.references.map(item => ({ text: item, color: '#1EA5C8', })), @@ -892,25 +938,27 @@ export async function extendedInformation( //--- SUMMARY TABLES let extraSummaryTables = []; if (Array.isArray(summaryTablesDefinitions[section][tab])) { - const tablesPromises = summaryTablesDefinitions[section][tab].map((summaryTable) => { - log('reporting:AlertsTable', `Fetching ${summaryTable.title} Table`, 'debug'); - const alertsSummaryTable = new SummaryTable( - context, - from, - to, - filters, - allowedAgentsFilter, - summaryTable, - pattern - ); - return alertsSummaryTable.fetch(); - }); + const tablesPromises = summaryTablesDefinitions[section][tab].map( + summaryTable => { + printer.logger.debug(`Fetching ${summaryTable.title} Table`); + const alertsSummaryTable = new SummaryTable( + context, + from, + to, + filters, + allowedAgentsFilter, + summaryTable, + pattern, + ); + return alertsSummaryTable.fetch(); + }, + ); extraSummaryTables = await Promise.all(tablesPromises); } return extraSummaryTables; } catch (error) { - log('reporting:extendedInformation', error.message || error); + printer.logger.error(error.message || error); return Promise.reject(error); } } diff --git a/plugins/main/server/lib/reporting/printer.ts b/plugins/main/server/lib/reporting/printer.ts index f31f2ea374..091a3cb082 100644 --- a/plugins/main/server/lib/reporting/printer.ts +++ b/plugins/main/server/lib/reporting/printer.ts @@ -5,16 +5,16 @@ import clockIconRaw from './clock-icon-raw'; import filterIconRaw from './filter-icon-raw'; import { AgentsVisualizations, - OverviewVisualizations + OverviewVisualizations, } from '../../integration-files/visualizations'; -import { log } from '../logger'; import * as TimSort from 'timsort'; import { getConfiguration } from '../get-configuration'; -import { REPORTS_PRIMARY_COLOR} from '../../../common/constants'; +import { REPORTS_PRIMARY_COLOR } from '../../../common/constants'; import { getCustomizationSetting } from '../../../common/services/settings'; +import { Logger } from 'opensearch-dashboards/server'; const COLORS = { - PRIMARY: REPORTS_PRIMARY_COLOR + PRIMARY: REPORTS_PRIMARY_COLOR, }; const pageConfiguration = ({ pathToLogo, pageHeader, pageFooter }) => ({ @@ -22,33 +22,33 @@ const pageConfiguration = ({ pathToLogo, pageHeader, pageFooter }) => ({ h1: { fontSize: 22, monslight: true, - color: COLORS.PRIMARY + color: COLORS.PRIMARY, }, h2: { fontSize: 18, monslight: true, - color: COLORS.PRIMARY + color: COLORS.PRIMARY, }, h3: { fontSize: 16, monslight: true, - color: COLORS.PRIMARY + color: COLORS.PRIMARY, }, h4: { fontSize: 14, monslight: true, - color: COLORS.PRIMARY + color: COLORS.PRIMARY, }, standard: { - color: '#333' + color: '#333', }, whiteColorFilters: { color: '#FFF', - fontSize: 14 + fontSize: 14, }, whiteColor: { - color: '#FFF' - } + color: '#FFF', + }, }, pageMargins: [40, 80, 40, 80], header: { @@ -56,16 +56,16 @@ const pageConfiguration = ({ pathToLogo, pageHeader, pageFooter }) => ({ columns: [ { image: path.join(__dirname, `../../../public/assets/${pathToLogo}`), - fit: [190, 50] + fit: [190, 50], }, { text: pageHeader, alignment: 'right', margin: [0, 0, 40, 0], color: COLORS.PRIMARY, - width: 'auto' - } - ] + width: 'auto', + }, + ], }, content: [], footer(currentPage, pageCount) { @@ -74,23 +74,22 @@ const pageConfiguration = ({ pathToLogo, pageHeader, pageFooter }) => ({ { text: pageFooter, color: COLORS.PRIMARY, - margin: [40, 40, 0, 0] + margin: [40, 40, 0, 0], }, { text: 'Page ' + currentPage.toString() + ' of ' + pageCount, alignment: 'right', margin: [0, 40, 40, 0], color: COLORS.PRIMARY, - width: 'auto' - } - ] + width: 'auto', + }, + ], }; }, pageBreakBefore(currentNode, followingNodesOnPage) { if (currentNode.id && currentNode.id.includes('splitvis')) { return ( - followingNodesOnPage.length === 6 || - followingNodesOnPage.length === 7 + followingNodesOnPage.length === 6 || followingNodesOnPage.length === 7 ); } if ( @@ -100,52 +99,49 @@ const pageConfiguration = ({ pathToLogo, pageHeader, pageFooter }) => ({ return followingNodesOnPage.length === 6; } return false; - } + }, }); const fonts = { Roboto: { normal: path.join( __dirname, - '../../../public/assets/fonts/opensans/OpenSans-Light.ttf' + '../../../public/assets/fonts/opensans/OpenSans-Light.ttf', ), bold: path.join( __dirname, - '../../../public/assets/fonts/opensans/OpenSans-Bold.ttf' + '../../../public/assets/fonts/opensans/OpenSans-Bold.ttf', ), italics: path.join( __dirname, - '../../../public/assets/fonts/opensans/OpenSans-Italic.ttf' + '../../../public/assets/fonts/opensans/OpenSans-Italic.ttf', ), bolditalics: path.join( __dirname, - '../../../public/assets/fonts/opensans/OpenSans-BoldItalic.ttf' + '../../../public/assets/fonts/opensans/OpenSans-BoldItalic.ttf', ), monslight: path.join( __dirname, - '../../../public/assets/fonts/opensans/Montserrat-Light.ttf' - ) - } + '../../../public/assets/fonts/opensans/Montserrat-Light.ttf', + ), + }, }; -export class ReportPrinter{ +export class ReportPrinter { private _content: any[]; private _printer: PdfPrinter; - constructor(){ + constructor(public logger: Logger) { this._printer = new PdfPrinter(fonts); this._content = []; } - addContent(...content: any){ + addContent(...content: any) { this._content.push(...content); return this; } - addConfigTables(tables: any){ - log( - 'reporting:renderConfigTables', - 'Started to render configuration tables', - 'info' + addConfigTables(tables: any) { + this.logger.debug( + `Started to render configuration tables: ${tables.length}`, ); - log('reporting:renderConfigTables', `tables: ${tables.length}`, 'debug'); for (const table of tables) { let rowsparsed = table.rows; if (Array.isArray(rowsparsed) && rowsparsed.length) { @@ -154,21 +150,22 @@ export class ReportPrinter{ this.addContent({ text: table.title, style: { fontSize: 11, color: '#000' }, - margin: table.title && table.type === 'table' ? [0, 0, 0, 5] : '' + margin: table.title && table.type === 'table' ? [0, 0, 0, 5] : '', }); if (table.title === 'Monitored directories') { this.addContent({ - text: - 'RT: Real time | WD: Who-data | Per.: Permission | MT: Modification time | SL: Symbolic link | RL: Recursion level', + text: 'RT: Real time | WD: Who-data | Per.: Permission | MT: Modification time | SL: Symbolic link | RL: Recursion level', style: { fontSize: 8, color: COLORS.PRIMARY }, - margin: [0, 0, 0, 5] + margin: [0, 0, 0, 5], }); } const full_body = []; - const modifiedRows = rows.map(row => row.map(cell => ({ text: cell || '-', style: 'standard' }))); + const modifiedRows = rows.map(row => + row.map(cell => ({ text: cell || '-', style: 'standard' })), + ); // for (const row of rows) { // modifiedRows.push( // row.map(cell => ({ text: cell || '-', style: 'standard' })) @@ -184,9 +181,9 @@ export class ReportPrinter{ text: col || '-', border: [0, 0, 0, 20], fontSize: 0, - colSpan: 2 + colSpan: 2, })), - ...modifiedRows + ...modifiedRows, ); this.addContent({ fontSize: 8, @@ -194,48 +191,48 @@ export class ReportPrinter{ headerRows: 0, widths, body: full_body, - dontBreakRows: true + dontBreakRows: true, }, layout: { fillColor: i => (i === 0 ? '#fff' : null), hLineColor: () => '#D3DAE6', hLineWidth: () => 1, - vLineWidth: () => 0 - } + vLineWidth: () => 0, + }, }); } else if (table.type === 'table') { full_body.push( table.columns.map(col => ({ text: col || '-', style: 'whiteColor', - border: [0, 0, 0, 0] + border: [0, 0, 0, 0], })), - ...modifiedRows + ...modifiedRows, ); this.addContent({ fontSize: 8, table: { headerRows: 1, widths, - body: full_body + body: full_body, }, layout: { fillColor: i => (i === 0 ? COLORS.PRIMARY : null), hLineColor: () => COLORS.PRIMARY, hLineWidth: () => 1, - vLineWidth: () => 0 - } + vLineWidth: () => 0, + }, }); } this.addNewLine(); } - log('reporting:renderConfigTables', `Table rendered`, 'debug'); + this.logger.debug('Table rendered'); } } - addTables(tables: any){ - log('reporting:renderTables', 'Started to render tables', 'info'); - log('reporting:renderTables', `tables: ${tables.length}`, 'debug'); + addTables(tables: any) { + this.logger.debug(`Started to render tables: ${tables.length}`); + for (const table of tables) { let rowsparsed = []; rowsparsed = table.rows; @@ -259,7 +256,9 @@ export class ReportPrinter{ TimSort.sort(rows, sortTableRows); - const modifiedRows = rows.map(row => row.map(cell => ({ text: cell || '-', style: 'standard' }))); + const modifiedRows = rows.map(row => + row.map(cell => ({ text: cell || '-', style: 'standard' })), + ); // the width of the columns is assigned const widths = Array(table.columns.length - 1).fill('auto'); @@ -269,42 +268,36 @@ export class ReportPrinter{ table.columns.map(col => ({ text: col || '-', style: 'whiteColor', - border: [0, 0, 0, 0] + border: [0, 0, 0, 0], })), - ...modifiedRows + ...modifiedRows, ); this.addContent({ fontSize: 8, table: { headerRows: 1, widths, - body: full_body + body: full_body, }, layout: { fillColor: i => (i === 0 ? COLORS.PRIMARY : null), hLineColor: () => COLORS.PRIMARY, hLineWidth: () => 1, - vLineWidth: () => 0 - } + vLineWidth: () => 0, + }, }); this.addNewLine(); - log('reporting:renderTables', `Table rendered`, 'debug'); + this.logger.debug('Table rendered'); } } } - addTimeRangeAndFilters(from, to, filters, timeZone){ - log( - 'reporting:renderTimeRangeAndFilters', - `Started to render the time range and the filters`, - 'info' - ); - log( - 'reporting:renderTimeRangeAndFilters', - `from: ${from}, to: ${to}, filters: ${filters}, timeZone: ${timeZone}`, - 'debug' + addTimeRangeAndFilters(from, to, filters, timeZone) { + this.logger.debug( + `Started to render the time range and the filters: from: ${from}, to: ${to}, filters: ${filters}, timeZone: ${timeZone}`, ); + const fromDate = new Date( - new Date(from).toLocaleString('en-US', { timeZone }) + new Date(from).toLocaleString('en-US', { timeZone }), ); const toDate = new Date(new Date(to).toLocaleString('en-US', { timeZone })); const str = `${this.formatDate(fromDate)} to ${this.formatDate(toDate)}`; @@ -321,15 +314,15 @@ export class ReportPrinter{ svg: clockIconRaw, width: 10, height: 10, - margin: [40, 5, 0, 0] + margin: [40, 5, 0, 0], }, { text: str || '-', margin: [43, 0, 0, 0], - style: 'whiteColorFilters' - } - ] - } + style: 'whiteColorFilters', + }, + ], + }, ], [ { @@ -338,39 +331,31 @@ export class ReportPrinter{ svg: filterIconRaw, width: 10, height: 10, - margin: [40, 6, 0, 0] + margin: [40, 6, 0, 0], }, { text: filters || '-', margin: [43, 0, 0, 0], - style: 'whiteColorFilters' - } - ] - } - ] - ] + style: 'whiteColorFilters', + }, + ], + }, + ], + ], }, margin: [-40, 0, -40, 0], layout: { fillColor: () => COLORS.PRIMARY, hLineWidth: () => 0, - vLineWidth: () => 0 - } + vLineWidth: () => 0, + }, }); this.addContent({ text: '\n' }); - log( - 'reporting:renderTimeRangeAndFilters', - 'Time range and filters rendered', - 'debug' - ); + this.logger.debug('Time range and filters rendered'); } - addVisualizations(visualizations, isAgents, tab){ - log( - 'reporting:renderVisualizations', - `${visualizations.length} visualizations for tab ${tab}`, - 'info' - ); + addVisualizations(visualizations, isAgents, tab) { + this.logger.debug(`${visualizations.length} visualizations for tab ${tab}`); const single_vis = visualizations.filter(item => item.width >= 600); const double_vis = visualizations.filter(item => item.width < 600); @@ -379,11 +364,13 @@ export class ReportPrinter{ this.addContent({ id: 'singlevis' + title[0]._source.title, text: title[0]._source.title, - style: 'h3' + style: 'h3', + }); + this.addContent({ + columns: [{ image: visualization.element, width: 500 }], }); - this.addContent({ columns: [{ image: visualization.element, width: 500 }] }); this.addNewLine(); - }) + }); let pair = []; @@ -399,22 +386,22 @@ export class ReportPrinter{ id: 'splitvis' + title_1[0]._source.title, text: title_1[0]._source.title, style: 'h3', - width: 280 + width: 280, }, { id: 'splitvis' + title_2[0]._source.title, text: title_2[0]._source.title, style: 'h3', - width: 280 - } - ] + width: 280, + }, + ], }); this.addContent({ columns: [ { image: pair[0].element, width: 270 }, - { image: pair[1].element, width: 270 } - ] + { image: pair[1].element, width: 270 }, + ], }); this.addNewLine(); @@ -431,16 +418,16 @@ export class ReportPrinter{ id: 'splitsinglevis' + title[0]._source.title, text: title[0]._source.title, style: 'h3', - width: 280 - } - ] + width: 280, + }, + ], }); this.addContent({ columns: [{ image: item.element, width: 280 }] }); this.addNewLine(); } } formatDate(date: Date): string { - log('reporting:formatDate', `Format date ${date}`, 'info'); + this.logger.debug(`Format date ${date}`); const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); @@ -452,16 +439,14 @@ export class ReportPrinter{ }T${hours < 10 ? '0' + hours : hours}:${ minutes < 10 ? '0' + minutes : minutes }:${seconds < 10 ? '0' + seconds : seconds}`; - log('reporting:formatDate', `str: ${str}`, 'debug'); + this.logger.debug(`str: ${str}`); return str; } checkTitle(item, isAgents, tab) { - log( - 'reporting:checkTitle', + this.logger.debug( `Item ID ${item.id}, from ${ isAgents ? 'agents' : 'overview' } and tab ${tab}`, - 'info' ); const title = isAgents @@ -470,17 +455,25 @@ export class ReportPrinter{ return title; } - addSimpleTable({columns, items, title}: {columns: ({id: string, label: string})[], title?: (string | {text: string, style: string}), items: any[]}){ - + addSimpleTable({ + columns, + items, + title, + }: { + columns: { id: string; label: string }[]; + title?: string | { text: string; style: string }; + items: any[]; + }) { if (title) { - this.addContent(typeof title === 'string' ? { text: title, style: 'h4' } : title) - .addNewLine(); + this.addContent( + typeof title === 'string' ? { text: title, style: 'h4' } : title, + ).addNewLine(); } if (!items || !items.length) { this.addContent({ text: 'No results match your search criteria', - style: 'standard' + style: 'standard', }); return this; } @@ -494,29 +487,27 @@ export class ReportPrinter{ const cellValue = item[column.id]; return { text: typeof cellValue !== 'undefined' ? cellValue : '-', - style: 'standard' - } - }) + style: 'standard', + }; + }); }); // 385 is the max initial width per column let totalLength = columns.length - 1; - const widthColumn = 385/totalLength; + const widthColumn = 385 / totalLength; let totalWidth = totalLength * widthColumn; - const widths:(number)[] = []; + const widths: number[] = []; for (let step = 0; step < columns.length - 1; step++) { - let columnLength = this.getColumnWidth(columns[step], tableRows, step); if (columnLength <= Math.round(totalWidth / totalLength)) { widths.push(columnLength); totalWidth -= columnLength; - } - else { + } else { widths.push(Math.round(totalWidth / totalLength)); - totalWidth -= Math.round((totalWidth / totalLength)); + totalWidth -= Math.round(totalWidth / totalLength); } totalLength--; } @@ -527,52 +518,51 @@ export class ReportPrinter{ table: { headerRows: 1, widths, - body: [tableHeader, ...tableRows] + body: [tableHeader, ...tableRows], }, layout: { fillColor: i => (i === 0 ? COLORS.PRIMARY : null), hLineColor: () => COLORS.PRIMARY, hLineWidth: () => 1, - vLineWidth: () => 0 - } + vLineWidth: () => 0, + }, }).addNewLine(); return this; } - addList({title, list}: {title: string | {text: string, style: string}, list: (string | {text: string, style: string})[]}){ - return this - .addContentWithNewLine(typeof title === 'string' ? {text: title, style: 'h2'} : title) - .addContent({ul: list.filter(element => element)}) + addList({ + title, + list, + }: { + title: string | { text: string; style: string }; + list: (string | { text: string; style: string })[]; + }) { + return this.addContentWithNewLine( + typeof title === 'string' ? { text: title, style: 'h2' } : title, + ) + .addContent({ ul: list.filter(element => element) }) .addNewLine(); } - addNewLine(){ - return this.addContent({text: '\n'}); + addNewLine() { + return this.addContent({ text: '\n' }); } - addContentWithNewLine(title: any){ + addContentWithNewLine(title: any) { return this.addContent(title).addNewLine(); } - addAgentsFilters(agents){ - log( - 'reporting:addAgentsFilters', - `Started to render the authorized agents filters`, - 'info' - ); - log( - 'reporting:addAgentsFilters', - `agents: ${agents}`, - 'debug' + addAgentsFilters(agents) { + this.logger.debug( + `Started to render the authorized agents filters: agents: ${agents}`, ); this.addNewLine(); this.addContent({ - text: - 'NOTE: This report only includes the authorized agents of the user who generated the report', + text: 'NOTE: This report only includes the authorized agents of the user who generated the report', style: { fontSize: 10, color: COLORS.PRIMARY }, - margin: [0, 0, 0, 5] + margin: [0, 0, 0, 5], }); /*TODO: This will be enabled by a config*/ @@ -609,11 +599,7 @@ export class ReportPrinter{ }); */ this.addContent({ text: '\n' }); - log( - 'reporting:addAgentsFilters', - 'Time range and filters rendered', - 'debug' - ); + this.logger.debug('Time range and filters rendered'); } async print(reportPath: string) { @@ -621,18 +607,28 @@ export class ReportPrinter{ try { const configuration = getConfiguration(); - const pathToLogo = getCustomizationSetting(configuration, 'customization.logo.reports'); - const pageHeader = getCustomizationSetting(configuration, 'customization.reports.header'); - const pageFooter = getCustomizationSetting(configuration, 'customization.reports.footer'); + const pathToLogo = getCustomizationSetting( + configuration, + 'customization.logo.reports', + ); + const pageHeader = getCustomizationSetting( + configuration, + 'customization.reports.header', + ); + const pageFooter = getCustomizationSetting( + configuration, + 'customization.reports.footer', + ); - const document = this._printer.createPdfKitDocument({ ...pageConfiguration({ pathToLogo, pageHeader, pageFooter }), content: this._content }); + const document = this._printer.createPdfKitDocument({ + ...pageConfiguration({ pathToLogo, pageHeader, pageFooter }), + content: this._content, + }); document.on('error', reject); document.on('end', resolve); - document.pipe( - fs.createWriteStream(reportPath) - ); + document.pipe(fs.createWriteStream(reportPath)); document.end(); } catch (ex) { reject(ex); @@ -648,13 +644,15 @@ export class ReportPrinter{ * @param step * @returns {number} */ - getColumnWidth(column, tableRows, index){ + getColumnWidth(column, tableRows, index) { const widthCharacter = 5; //min width per character //Get the longest row value - const maxRowLength = tableRows.reduce((maxLength, row)=>{ - return (row[index].text.length > maxLength ? row[index].text.length : maxLength); - },0); + const maxRowLength = tableRows.reduce((maxLength, row) => { + return row[index].text.length > maxLength + ? row[index].text.length + : maxLength; + }, 0); //Get column name length const headerLength = column.label.length; diff --git a/plugins/main/server/lib/security-factory/factories/default-factory.ts b/plugins/main/server/lib/security-factory/factories/default-factory.ts deleted file mode 100644 index 4c29ef5130..0000000000 --- a/plugins/main/server/lib/security-factory/factories/default-factory.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ISecurityFactory } from '../'; -import { OpenSearchDashboardsRequest, RequestHandlerContext } from 'src/core/server'; -import { ELASTIC_NAME } from '../../../../common/constants'; -import md5 from 'md5'; - -export class DefaultFactory implements ISecurityFactory{ - platform: string = ''; - async getCurrentUser(request: OpenSearchDashboardsRequest, context?:RequestHandlerContext) { - return { - username: ELASTIC_NAME, - authContext: { username: ELASTIC_NAME }, - hashUsername: md5(ELASTIC_NAME) - }; - } -} diff --git a/plugins/main/server/lib/security-factory/factories/index.ts b/plugins/main/server/lib/security-factory/factories/index.ts deleted file mode 100644 index b02efdd30a..0000000000 --- a/plugins/main/server/lib/security-factory/factories/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { OpenSearchDashboardsSecurityFactory } from './opensearch-dashboards-security-factory'; -export { DefaultFactory } from './default-factory'; \ No newline at end of file diff --git a/plugins/main/server/lib/security-factory/factories/opensearch-dashboards-security-factory.ts b/plugins/main/server/lib/security-factory/factories/opensearch-dashboards-security-factory.ts deleted file mode 100644 index b0cf81dbfc..0000000000 --- a/plugins/main/server/lib/security-factory/factories/opensearch-dashboards-security-factory.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ISecurityFactory } from '..' -import { OpenSearchDashboardsRequest, RequestHandlerContext } from 'src/core/server'; -import { WAZUH_SECURITY_PLUGIN_OPENSEARCH_DASHBOARDS_SECURITY } from '../../../../common/constants'; -import md5 from 'md5'; - -export class OpenSearchDashboardsSecurityFactory implements ISecurityFactory { - platform: string = WAZUH_SECURITY_PLUGIN_OPENSEARCH_DASHBOARDS_SECURITY; - - constructor(private securityDashboards: any) { - } - - async getCurrentUser(request: OpenSearchDashboardsRequest, context:RequestHandlerContext) { - try { - const params = { - path: `/_opendistro/_security/api/account`, - method: 'GET', - }; - - const {body: authContext} = await context.core.opensearch.client.asCurrentUser.transport.request(params); - const username = this.getUserName(authContext); - return { username, authContext, hashUsername: md5(username) }; - } catch (error) { - throw error; - } - } - - getUserName(authContext:any) { - return authContext['user_name'] - } -} diff --git a/plugins/main/server/lib/security-factory/index.ts b/plugins/main/server/lib/security-factory/index.ts deleted file mode 100644 index 629d004a60..0000000000 --- a/plugins/main/server/lib/security-factory/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ISecurityFactory, SecurityObj} from './security-factory'; \ No newline at end of file diff --git a/plugins/main/server/lib/security-factory/security-factory.ts b/plugins/main/server/lib/security-factory/security-factory.ts deleted file mode 100644 index e1df0e11ce..0000000000 --- a/plugins/main/server/lib/security-factory/security-factory.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { OpenSearchDashboardsSecurityFactory, DefaultFactory } from './factories'; -import { OpenSearchDashboardsRequest, RequestHandlerContext } from 'src/core/server'; -import { PluginSetup } from '../../types'; - -type CurrentUser = { - username?: string; - authContext: { [key: string]: any }; -}; - -export interface ISecurityFactory { - platform?: string; - getCurrentUser(request: OpenSearchDashboardsRequest, context?: RequestHandlerContext): Promise; -} - -export async function SecurityObj( - { securityDashboards }: PluginSetup -): Promise { - return !!securityDashboards - ? new OpenSearchDashboardsSecurityFactory(securityDashboards) - : new DefaultFactory(); -} diff --git a/plugins/main/server/lib/update-registry.ts b/plugins/main/server/lib/update-registry.ts deleted file mode 100644 index 433daf5839..0000000000 --- a/plugins/main/server/lib/update-registry.ts +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Wazuh app - Module to update the configuration file - * 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 fs from 'fs'; -import { log } from './logger'; -import { WAZUH_DATA_CONFIG_REGISTRY_PATH } from '../../common/constants'; - -export class UpdateRegistry { - busy: boolean; - file: string; - constructor() { - this.busy = false; - this.file = WAZUH_DATA_CONFIG_REGISTRY_PATH; - } - - /** - * Reads the Wazuh registry content - */ - async readContent() { - try { - log( - 'update-registry:readContent', - 'Reading wazuh-registry.json content', - 'debug', - ); - const content = await fs.readFileSync(this.file, { encoding: 'utf-8' }); - return JSON.parse(content); - } catch (error) { - log('update-registry:readContent', error.message || error); - return Promise.reject(error); - } - } - - /** - * Get the hosts and their cluster info stored in the registry - */ - async getHosts() { - try { - log('update-registry:getHosts', 'Getting hosts from registry', 'debug'); - const content = await this.readContent(); - return content.hosts || {}; - } catch (error) { - log('update-registry:getHosts', error.message || error); - return Promise.reject(error); - } - } - - /** - * Returns the cluster information associated to an API id - * @param {String} id - */ - async getHostById(id) { - try { - if (!id) throw new Error('API id is missing'); - const hosts = await this.getHosts(); - return hosts.id || {}; - } catch (error) { - log('update-registry:getClusterInfoByAPI', error.message || error); - return Promise.reject(error); - } - } - - /** - * Writes the wazuh-registry.json - * @param {Object} content - */ - async writeContent(content) { - try { - log( - 'update-registry:writeContent', - 'Writting wazuh-registry.json content', - 'debug', - ); - if (this.busy) { - throw new Error('Another process is updating the registry file'); - } - this.busy = true; - await fs.writeFileSync(this.file, JSON.stringify(content)); - this.busy = false; - } catch (error) { - log('update-registry:writeContent', error.message || error); - return Promise.reject(error); - } - } - - /** - * Checks if the host exist in order to update the data, otherwise creates it - * @param {String} id - * @param {Object} hosts - */ - checkHost(id, hosts) { - try { - return Object.keys(hosts).includes(id); - } catch (error) { - log('update-registry:checkHost', error.message || error); - return Promise.reject(error); - } - } - - /** - * Migrates the cluster information associated to an API id - * @param {String} id - * @param {Object} clusterInfo - */ - async migrateToRegistry(id, clusterInfo) { - try { - const content = await this.readContent(); - if (!Object.keys(content).includes('hosts')) { - Object.assign(content, { hosts: {} }); - } - const info = { cluster_info: clusterInfo }; - content.hosts[id] = info; - await this.writeContent(content); - log( - 'update-registry:migrateToRegistry', - `API ${id} was properly migrated`, - 'debug', - ); - return info; - } catch (error) { - log('update-registry:migrateToRegistry', error.message || error); - return Promise.reject(error); - } - } - - /** - * Updates the cluster-information or manager-information in the registry - * @param {String} id - * @param {Object} clusterInfo - */ - async updateClusterInfo(id, clusterInfo) { - try { - const content = await this.readContent(); - // Checks if not exists in order to create - if (!content.hosts[id]) content.hosts[id] = {}; - content.hosts[id].cluster_info = clusterInfo; - await this.writeContent(content); - log( - 'update-registry:updateClusterInfo', - `API ${id} information was properly updated`, - 'debug', - ); - return id; - } catch (error) { - log('update-registry:updateClusterInfo', error.message || error); - return Promise.reject(error); - } - } - - /** - * Remove the given ids from the registry host entries - * @param {Array} ids - */ - async removeHostEntries(ids) { - try { - log('update-registry:removeHostEntry', 'Removing entry', 'debug'); - const content = await this.readContent(); - ids.forEach(id => delete content.hosts[id]); - await this.writeContent(content); - } catch (error) { - log('update-registry:removeHostEntry', error.message || error); - return Promise.reject(error); - } - } - - /** - * Compare the hosts from wazuh.yml and the host in the wazuh-registry.json file in order to remove the orphan registry register - * @param {Array} hosts - */ - async removeOrphanEntries(hosts) { - try { - log( - 'update-registry:removeOrphanEntries', - 'Checking orphan registry entries', - 'debug', - ); - const entries = await this.getHosts(); - const hostsKeys = hosts.map(h => { - return h.id; - }); - const entriesKeys = Object.keys(entries); - const diff = entriesKeys.filter(e => { - return !hostsKeys.includes(e); - }); - await this.removeHostEntries(diff); - } catch (error) { - log('update-registry:removeOrphanEntries', error.message || error); - return Promise.reject(error); - } - } - - /** - * Returns the token information associated to an API id - * @param {String} id - */ - async getTokenById(id) { - try { - if (!id) throw new Error('API id is missing'); - const hosts = await this.getHosts(); - return hosts[id] ? hosts[id].token || null : null; - } catch (error) { - log('update-registry:getTokenById', error.message || error); - return Promise.reject(error); - } - } - - /** - * Updates the token in the registry - * @param {String} id - * @param {String} token - */ - async updateTokenByHost(id, token) { - try { - const content = await this.readContent(); - // Checks if not exists in order to create - if (!content.hosts[id]) content.hosts[id] = {}; - content.hosts[id].token = token; - await this.writeContent(content); - log( - 'update-registry:updateToken', - `API ${id} information was properly updated`, - 'debug', - ); - return id; - } catch (error) { - log('update-registry:updateToken', error.message || error); - return Promise.reject(error); - } - } -} diff --git a/plugins/main/server/plugin.ts b/plugins/main/server/plugin.ts index 788d281ea2..49d8012e89 100644 --- a/plugins/main/server/plugin.ts +++ b/plugins/main/server/plugin.ts @@ -27,33 +27,44 @@ import { } from 'opensearch_dashboards/server'; import { WazuhPluginSetup, WazuhPluginStart, PluginSetup } from './types'; -import { SecurityObj, ISecurityFactory } from './lib/security-factory'; import { setupRoutes } from './routes'; -import { jobInitializeRun, jobMonitoringRun, jobSchedulerRun, jobQueueRun, jobMigrationTasksRun } from './start'; -import { getCookieValueByName } from './lib/cookie'; -import * as ApiInterceptor from './lib/api-interceptor'; -import { schema, TypeOf } from '@osd/config-schema'; -import type { Observable } from 'rxjs'; +import { + jobInitializeRun, + jobMonitoringRun, + jobSchedulerRun, + jobQueueRun, + jobMigrationTasksRun, +} from './start'; import { first } from 'rxjs/operators'; declare module 'opensearch_dashboards/server' { interface RequestHandlerContext { wazuh: { - logger: Logger, - plugins: PluginSetup, - security: ISecurityFactory + logger: Logger; + plugins: PluginSetup; + security: any; api: { client: { asInternalUser: { - authenticate: (apiHostID: string) => Promise - request: (method: string, path: string, data: any, options: {apiHostID: string, forceRefresh?:boolean}) => Promise - }, + authenticate: (apiHostID: string) => Promise; + request: ( + method: string, + path: string, + data: any, + options: { apiHostID: string; forceRefresh?: boolean }, + ) => Promise; + }; asCurrentUser: { - authenticate: (apiHostID: string) => Promise - request: (method: string, path: string, data: any, options: {apiHostID: string, forceRefresh?:boolean}) => Promise - } - } - } + authenticate: (apiHostID: string) => Promise; + request: ( + method: string, + path: string, + data: any, + options: { apiHostID: string; forceRefresh?: boolean }, + ) => Promise; + }; + }; + }; }; } } @@ -68,29 +79,20 @@ export class WazuhPlugin implements Plugin { public async setup(core: CoreSetup, plugins: PluginSetup) { this.logger.debug('Wazuh-wui: Setup'); - const wazuhSecurity = await SecurityObj(plugins); const serverInfo = core.http.getServerInfo(); core.http.registerRouteHandlerContext('wazuh', (context, request) => { return { - logger: this.logger, + // Create a custom logger with a tag composed of HTTP method and path endpoint + logger: this.logger.get( + `${request.route.method.toUpperCase()} ${request.route.path}`, + ), server: { info: serverInfo, }, plugins, - security: wazuhSecurity, - api: { - client: { - asInternalUser: { - authenticate: async (apiHostID) => await ApiInterceptor.authenticate(apiHostID), - request: async (method, path, data, options) => await ApiInterceptor.requestAsInternalUser(method, path, data, options), - }, - asCurrentUser: { - authenticate: async (apiHostID) => await ApiInterceptor.authenticate(apiHostID, (await wazuhSecurity.getCurrentUser(request, context)).authContext), - request: async (method, path, data, options) => await ApiInterceptor.requestAsCurrentUser(method, path, data, {...options, token: getCookieValueByName(request.headers.cookie, 'wz-token')}), - } - } - } + security: plugins.wazuhCore.dashboardSecurity, + api: context.wazuh_core.api, }; }); @@ -109,19 +111,14 @@ export class WazuhPlugin implements Plugin { return {}; } - public async start(core: CoreStart) { - const globalConfiguration: SharedGlobalConfig = await this.initializerContext.config.legacy.globalConfig$.pipe(first()).toPromise(); - const wazuhApiClient = { - client: { - asInternalUser: { - authenticate: async (apiHostID) => await ApiInterceptor.authenticate(apiHostID), - request: async (method, path, data, options) => await ApiInterceptor.requestAsInternalUser(method, path, data, options), - } - } - }; + public async start(core: CoreStart, plugins: any) { + const globalConfiguration: SharedGlobalConfig = + await this.initializerContext.config.legacy.globalConfig$ + .pipe(first()) + .toPromise(); const contextServer = { - config: globalConfiguration + config: globalConfiguration, }; // Initialize @@ -129,19 +126,21 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('initialize'), - api: wazuhApiClient + api: plugins.wazuhCore.api, }, - server: contextServer + wazuh_core: plugins.wazuhCore, + server: contextServer, }); // Migration tasks jobMigrationTasksRun({ - core, + core, wazuh: { logger: this.logger.get('migration-task'), - api: wazuhApiClient + api: plugins.wazuhCore.api, }, - server: contextServer + wazuh_core: plugins.wazuhCore, + server: contextServer, }); // Monitoring @@ -149,9 +148,10 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('monitoring'), - api: wazuhApiClient + api: plugins.wazuhCore.api, }, - server: contextServer + wazuh_core: plugins.wazuhCore, + server: contextServer, }); // Scheduler @@ -159,9 +159,10 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('cron-scheduler'), - api: wazuhApiClient + api: plugins.wazuhCore.api, }, - server: contextServer + wazuh_core: plugins.wazuhCore, + server: contextServer, }); // Queue @@ -169,12 +170,13 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('queue'), - api: wazuhApiClient + api: plugins.wazuhCore.api, }, - server: contextServer + wazuh_core: plugins.wazuhCore, + server: contextServer, }); return {}; } - public stop() { } + public stop() {} } diff --git a/plugins/main/server/routes/wazuh-api-http-status.test.ts b/plugins/main/server/routes/wazuh-api-http-status.test.ts index 5a7edad40c..ceb32b4014 100644 --- a/plugins/main/server/routes/wazuh-api-http-status.test.ts +++ b/plugins/main/server/routes/wazuh-api-http-status.test.ts @@ -6,13 +6,15 @@ import { loggingSystemMock } from '../../../../src/core/server/logging/logging_s import { ByteSizeValue } from '@osd/config-schema'; import supertest from 'supertest'; import { WazuhApiRoutes } from './wazuh-api'; -import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../lib/filesystem'; +import { + createDataDirectoryIfNotExists, + createDirectoryIfNotExists, +} from '../lib/filesystem'; import { HTTP_STATUS_CODES, WAZUH_DATA_ABSOLUTE_PATH, WAZUH_DATA_CONFIG_APP_PATH, WAZUH_DATA_CONFIG_DIRECTORY_PATH, - WAZUH_DATA_LOGS_DIRECTORY_PATH } from '../../common/constants'; import { execSync } from 'child_process'; import fs from 'fs'; @@ -22,22 +24,49 @@ const logger = loggingService.get(); const context = { wazuh: { security: { - getCurrentUser: () => 'wazuh' - } - } + getCurrentUser: () => 'wazuh', + }, + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, + }, + wazuh_core: { + manageHosts: { + getHostById: jest.fn(id => { + return { + id, + url: 'https://localhost', + port: 55000, + username: 'wazuh-wui', + password: 'wazuh-wui', + run_as: false, + }; + }), + }, + cacheAPIUserAllowRunAs: { + set: jest.fn(), + API_USER_STATUS_RUN_AS: { + ALL_DISABLED: 0, + USER_NOT_ALLOWED: 1, + HOST_DISABLED: 2, + ENABLED: 3, + }, + }, + }, }; -const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, context); +const enhanceWithContext = (fn: (...args: any[]) => any) => + fn.bind(null, context); let server, innerServer; beforeAll(async () => { - // Create /data/wazuh directory. createDataDirectoryIfNotExists(); // Create /data/wazuh/config directory. createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); - // Create /data/wazuh/logs directory. - createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); // Create server const config = { @@ -54,7 +83,11 @@ beforeAll(async () => { } as any; server = new HttpServer(loggingService, 'tests'); const router = new Router('', logger, enhanceWithContext); - const { registerRouter, server: innerServerTest, ...rest } = await server.setup(config); + const { + registerRouter, + server: innerServerTest, + ...rest + } = await server.setup(config); innerServer = innerServerTest; // Register routes @@ -101,13 +134,16 @@ hosts: }); it.each` - apiId | statusCode + apiId | statusCode ${'default'} | ${HTTP_STATUS_CODES.SERVICE_UNAVAILABLE} - `(`Get API configuration POST /api/check-api - apiID - $statusCode`, async ({ apiId, statusCode }) => { - const body = { id: apiId, forceRefresh: false }; - const response = await supertest(innerServer.listener) - .post('/api/check-api') - .send(body) - .expect(statusCode); - }); + `( + `Get API configuration POST /api/check-api - apiID - $statusCode`, + async ({ apiId, statusCode }) => { + const body = { id: apiId, forceRefresh: false }; + const response = await supertest(innerServer.listener) + .post('/api/check-api') + .send(body) + .expect(statusCode); + }, + ); }); diff --git a/plugins/main/server/routes/wazuh-reporting.test.ts b/plugins/main/server/routes/wazuh-reporting.test.ts index 495ae16cbe..075f411d7b 100644 --- a/plugins/main/server/routes/wazuh-reporting.test.ts +++ b/plugins/main/server/routes/wazuh-reporting.test.ts @@ -18,7 +18,6 @@ import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_DATA_CONFIG_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, - WAZUH_DATA_LOGS_DIRECTORY_PATH, WAZUH_DATA_ABSOLUTE_PATH, WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, } from '../../common/constants'; @@ -43,6 +42,23 @@ const context = { return { username, hashUsername: md5(username) }; }, }, + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + get() { + return { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + }, + }, + }, + wazuh_core: { + updateConfigurationFile: { updateConfiguration: jest.fn() }, }, }; const enhanceWithContext = (fn: (...args: any[]) => any) => @@ -57,9 +73,6 @@ beforeAll(async () => { // Create /data/wazuh/config directory. createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); - // Create /data/wazuh/logs directory. - createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); - // Create server const config = { name: 'plugin_platform', @@ -128,9 +141,6 @@ describe('[endpoint] GET /reports', () => { // Create /data/wazuh/config directory. createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); - // Create /data/wazuh/logs directory. - createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); - // Create /data/wazuh/downloads directory. createDirectoryIfNotExists(WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH); @@ -247,7 +257,7 @@ describe('[endpoint] PUT /utils/configuration', () => { .put('/utils/configuration') .send(configurationBody) .expect(responseStatusCode); - + return; if (typeof footer == 'string') { expect( responseConfig.body?.data?.updatedConfiguration?.[ diff --git a/plugins/main/server/routes/wazuh-utils/ui-logs.ts b/plugins/main/server/routes/wazuh-utils/ui-logs.ts index 0298e630e9..cbec450b06 100644 --- a/plugins/main/server/routes/wazuh-utils/ui-logs.ts +++ b/plugins/main/server/routes/wazuh-utils/ui-logs.ts @@ -15,13 +15,6 @@ import { schema } from '@osd/config-schema'; export const UiLogsRoutes = (router: IRouter) => { const ctrl = new UiLogsCtrl(); - router.get( - { - path: '/utils/logs/ui', - validate: false, - }, - async (context, request, response) => await ctrl.getUiLogs(response) - ); router.post( { @@ -34,6 +27,7 @@ export const UiLogsRoutes = (router: IRouter) => { }), }, }, - async (context, request, response) => await ctrl.createUiLogs(request, response) + async (context, request, response) => + await ctrl.createUiLogs(context, request, response), ); }; diff --git a/plugins/main/server/routes/wazuh-utils/wazuh-utils.test.ts b/plugins/main/server/routes/wazuh-utils/wazuh-utils.test.ts index 78399a5cdc..b41f9d3fb8 100644 --- a/plugins/main/server/routes/wazuh-utils/wazuh-utils.test.ts +++ b/plugins/main/server/routes/wazuh-utils/wazuh-utils.test.ts @@ -16,8 +16,6 @@ import { WAZUH_DATA_ABSOLUTE_PATH, WAZUH_DATA_CONFIG_APP_PATH, WAZUH_DATA_CONFIG_DIRECTORY_PATH, - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_DATA_LOGS_RAW_PATH, } from '../../../common/constants'; import { execSync } from 'child_process'; import fs from 'fs'; @@ -28,6 +26,9 @@ const loggingService = loggingSystemMock.create(); const logger = loggingService.get(); const context = { wazuh: {}, + wazuh_core: { + updateConfigurationFile: { updateConfiguration: jest.fn() }, + }, }; const enhanceWithContext = (fn: (...args: any[]) => any) => @@ -40,9 +41,6 @@ beforeAll(async () => { // Create /data/wazuh/config directory. createDirectoryIfNotExists(WAZUH_DATA_CONFIG_DIRECTORY_PATH); - // Create /data/wazuh/logs directory. - createDirectoryIfNotExists(WAZUH_DATA_LOGS_DIRECTORY_PATH); - // Create server const config = { name: 'plugin_platform', @@ -159,9 +157,9 @@ hosts: }); it.each` - settings | responseStatusCode - ${{ pattern: 'test-alerts-groupA-*' }} | ${200} - ${{ pattern: 'test-alerts-groupA-*', 'logs.level': 'debug' }} | ${200} + settings | responseStatusCode + ${{ pattern: 'test-alerts-groupA-*' }} | ${200} + ${{ pattern: 'test-alerts-groupA-*', timeout: 15000 }} | ${200} `( `Update the plugin configuration: $settings. PUT /utils/configuration - $responseStatusCode`, async ({ responseStatusCode, settings }) => { @@ -186,7 +184,7 @@ hosts: }, { testTitle: 'Update the plugin configuration', - settings: { pattern: 'test-alerts-groupA-*', 'logs.level': 'debug' }, + settings: { pattern: 'test-alerts-groupA-*', timeout: 15000 }, responseStatusCode: 200, responseBodyMessage: null, }, @@ -208,7 +206,7 @@ hosts: testTitle: 'Bad request, unknown setting', settings: { 'unknown.setting': 'test-alerts-groupA-*', - 'logs.level': 'debug', + timeout: 15000, }, responseStatusCode: 400, responseBodyMessage: @@ -381,9 +379,6 @@ hosts: ${'ip.ignore'} | ${['test', 'test#']} | ${400} | ${'[request body.ip.ignore.1]: It can\'t contain invalid characters: \\, /, ?, ", <, >, |, ,, #.'} ${'ip.selector'} | ${true} | ${200} | ${null} ${'ip.selector'} | ${''} | ${400} | ${'[request body.ip.selector]: expected value of type [boolean] but got [string]'} - ${'logs.level'} | ${'info'} | ${200} | ${null} - ${'logs.level'} | ${'debug'} | ${200} | ${null} - ${'logs.level'} | ${''} | ${400} | ${'[request body.logs.level]: types that failed validation:\n- [request body.logs.level.0]: expected value to equal [info]\n- [request body.logs.level.1]: expected value to equal [debug]'} ${'pattern'} | ${'test'} | ${200} | ${null} ${'pattern'} | ${'test*'} | ${200} | ${null} ${'pattern'} | ${''} | ${400} | ${'[request body.pattern]: Value can not be empty.'} @@ -682,27 +677,3 @@ hosts: }, ); }); - -describe('[endpoint] GET /utils/logs', () => { - beforeAll(() => { - // Create the configuration file with custom content - const fileContent = `--- -{"date":"2022-09-20T08:36:16.688Z","level":"info","location":"initialize","message":"Kibana index: .kibana"} -{"date":"2022-09-20T08:36:16.689Z","level":"info","location":"initialize","message":"App revision: 4400"} -`; - fs.writeFileSync(WAZUH_DATA_LOGS_RAW_PATH, fileContent, 'utf8'); - }); - - afterAll(() => { - // Remove the configuration file - fs.unlinkSync(WAZUH_DATA_LOGS_RAW_PATH); - }); - - it(`Get plugin logs. GET /utils/logs`, async () => { - const response = await supertest(innerServer.listener) - .get('/utils/logs') - .expect(200); - - expect(response.body.lastLogs).toBeDefined(); - }); -}); diff --git a/plugins/main/server/routes/wazuh-utils/wazuh-utils.ts b/plugins/main/server/routes/wazuh-utils/wazuh-utils.ts index 4e7821270f..37253fafb7 100644 --- a/plugins/main/server/routes/wazuh-utils/wazuh-utils.ts +++ b/plugins/main/server/routes/wazuh-utils/wazuh-utils.ts @@ -12,7 +12,11 @@ import { WazuhUtilsCtrl } from '../../controllers'; import { IRouter } from 'opensearch_dashboards/server'; import { schema } from '@osd/config-schema'; -import { CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, EpluginSettingType, PLUGIN_SETTINGS } from '../../../common/constants'; +import { + CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, + EpluginSettingType, + PLUGIN_SETTINGS, +} from '../../../common/constants'; export function WazuhUtilsRoutes(router: IRouter) { const ctrl = new WazuhUtilsCtrl(); @@ -21,9 +25,10 @@ export function WazuhUtilsRoutes(router: IRouter) { router.get( { path: '/utils/configuration', - validate: false + validate: false, }, - async (context, request, response) => ctrl.getConfigurationFile(context, request, response) + async (context, request, response) => + ctrl.getConfigurationFile(context, request, response), ); // Returns the wazuh.yml file in raw @@ -40,21 +45,28 @@ export function WazuhUtilsRoutes(router: IRouter) { [pluginSettingKey]: schema.maybe( pluginSettingConfiguration.validateBackend ? pluginSettingConfiguration.validateBackend(schema) - : schema.any() + : schema.any(), ), }), - {} - ) - ) - } + {}, + ), + ), + }, }, - async (context, request, response) => ctrl.updateConfigurationFile(context, request, response) + async (context, request, response) => + ctrl.updateConfigurationFile(context, request, response), ); - const pluginSettingsTypeFilepicker = Object.entries(PLUGIN_SETTINGS) - .filter(([_, { type, isConfigurableFromFile }]) => type === EpluginSettingType.filepicker && isConfigurableFromFile); + const pluginSettingsTypeFilepicker = Object.entries(PLUGIN_SETTINGS).filter( + ([_, { type, isConfigurableFromFile }]) => + type === EpluginSettingType.filepicker && isConfigurableFromFile, + ); - const schemaPluginSettingsTypeFilepicker = schema.oneOf(pluginSettingsTypeFilepicker.map(([pluginSettingKey]) => schema.literal(pluginSettingKey))); + const schemaPluginSettingsTypeFilepicker = schema.oneOf( + pluginSettingsTypeFilepicker.map(([pluginSettingKey]) => + schema.literal(pluginSettingKey), + ), + ); // Upload an asset router.put( @@ -63,20 +75,22 @@ export function WazuhUtilsRoutes(router: IRouter) { validate: { params: schema.object({ // key parameter should be a plugin setting of `filepicker` type - key: schemaPluginSettingsTypeFilepicker + key: schemaPluginSettingsTypeFilepicker, }), body: schema.object({ // file: buffer file: schema.buffer(), - }) + }), }, options: { body: { - maxBytes: CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, + maxBytes: + CUSTOMIZATION_ENDPOINT_PAYLOAD_UPLOAD_CUSTOM_FILE_MAXIMUM_BYTES, }, - } + }, }, - async (context, request, response) => ctrl.uploadFile(context, request, response) + async (context, request, response) => + ctrl.uploadFile(context, request, response), ); // Remove an asset @@ -86,19 +100,11 @@ export function WazuhUtilsRoutes(router: IRouter) { validate: { params: schema.object({ // key parameter should be a plugin setting of `filepicker` type - key: schemaPluginSettingsTypeFilepicker - }) - } - }, - async (context, request, response) => ctrl.deleteFile(context, request, response) - ); - - // Returns Wazuh app logs - router.get( - { - path: '/utils/logs', - validate: false + key: schemaPluginSettingsTypeFilepicker, + }), + }, }, - async (context, request, response) => ctrl.getAppLogs(context, request, response) + async (context, request, response) => + ctrl.deleteFile(context, request, response), ); } diff --git a/plugins/main/server/start/cron-scheduler/apiRequest.ts b/plugins/main/server/start/cron-scheduler/apiRequest.ts index fbf224b7f5..9ba30c14f8 100644 --- a/plugins/main/server/start/cron-scheduler/apiRequest.ts +++ b/plugins/main/server/start/cron-scheduler/apiRequest.ts @@ -1,61 +1,12 @@ -import { AxiosResponse }from 'axios'; -import * as ApiInterceptor from '../../lib/api-interceptor.js'; - export interface IApi { - id: string - user: string - password: string - url: string - port: number + id: string; + user: string; + password: string; + url: string; + port: number; cluster_info: { - manager: string - cluster: 'Disabled' | 'Enabled' - status: 'disabled' | 'enabled' - } + manager: string; + cluster: 'Disabled' | 'Enabled'; + status: 'disabled' | 'enabled'; + }; } - -export class ApiRequest { - private api: IApi; - private request: string; - private params: {}; - - constructor(request:string, api:IApi, params:{}={}, ) { - this.request = request; - this.api = api; - this.params = params; - } - - private async makeRequest():Promise { - const {id, url, port} = this.api; - - const response: AxiosResponse = await ApiInterceptor.requestAsInternalUser( - 'GET', - '/${this.request}', - this.params, - {apiHostID: id } - ) - return response; - } - - public async getData():Promise { - try { - const response = await this.makeRequest(); - if (response.status !== 200) throw response; - return response.data; - } catch (error) { - if (error.status === 404) { - throw {error: 404, message: error.data.detail}; - } - if (error.response && error.response.status === 401){ - throw {error: 401, message: 'Wrong Wazuh API credentials used'}; - } - if (error && error.data && error.data.detail && error.data.detail === 'ECONNRESET') { - throw {error: 3005, message: 'Wrong protocol being used to connect to the Wazuh API'}; - } - if (error && error.data && error.data.detail && ['ENOTFOUND','EHOSTUNREACH','EINVAL','EAI_AGAIN','ECONNREFUSED'].includes(error.data.detail)) { - throw {error: 3005, message: 'Wazuh API is not reachable. Please check your url and port.'}; - } - throw error; - } - } -} \ No newline at end of file diff --git a/plugins/main/server/start/cron-scheduler/error-handler.ts b/plugins/main/server/start/cron-scheduler/error-handler.ts index e1ad63be95..bcf465f836 100644 --- a/plugins/main/server/start/cron-scheduler/error-handler.ts +++ b/plugins/main/server/start/cron-scheduler/error-handler.ts @@ -1,23 +1,17 @@ -import { log } from '../../lib/logger'; -import { getConfiguration } from '../../lib/get-configuration'; - const DEBUG = 'debug'; const INFO = 'info'; const ERROR = 'error'; -function logLevel(level: string){ - return level === DEBUG ? INFO : level; -}; - export function ErrorHandler(error, serverLogger) { - const { ['logs.level']: logsLevel } = getConfiguration(); const errorLevel = ErrorLevels[error.error] || ERROR; - log('Cron-scheduler', error, errorLevel === ERROR ? INFO : errorLevel); try { - if (errorLevel === DEBUG && logsLevel !== DEBUG) return; - serverLogger[logLevel(errorLevel)](`${error instanceof Error ? error.toString() : JSON.stringify(error)}`); + serverLogger[errorLevel]( + `${error instanceof Error ? error.toString() : JSON.stringify(error)}`, + ); } catch (error) { - serverLogger[logLevel(errorLevel)](`Message too long to show in console output, check the log file`) + serverLogger.error( + `Message too long to show in console output, check the log file`, + ); } } @@ -34,4 +28,4 @@ const ErrorLevels = { 10005: DEBUG, 10006: DEBUG, 10007: DEBUG, -} \ No newline at end of file +}; diff --git a/plugins/main/server/start/cron-scheduler/save-document.ts b/plugins/main/server/start/cron-scheduler/save-document.ts index 87b2203a37..f8abf8e19d 100644 --- a/plugins/main/server/start/cron-scheduler/save-document.ts +++ b/plugins/main/server/start/cron-scheduler/save-document.ts @@ -1,22 +1,24 @@ import { BulkIndexDocumentsParams } from 'elasticsearch'; import { getConfiguration } from '../../lib/get-configuration'; -import { log } from '../../lib/logger'; import { indexDate } from '../../lib/index-date'; -import { WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS } from '../../../common/constants'; +import { + WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, + WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, +} from '../../../common/constants'; import { tryCatchForIndexPermissionError } from '../tryCatchForIndexPermissionError'; +import { getSettingDefaultValue } from '../../../common/services/settings'; export interface IIndexConfiguration { - name: string - creation: 'h' | 'd' | 'w' | 'm' - mapping?: string - shards?: number - replicas?: number + name: string; + creation: 'h' | 'd' | 'w' | 'm'; + mapping?: string; + shards?: number; + replicas?: number; } export class SaveDocument { context: any; esClientInternalUser: any; - logPath = 'cron-scheduler|SaveDocument'; constructor(context) { this.context = context; @@ -28,38 +30,67 @@ export class SaveDocument { const index = this.addIndexPrefix(name); const indexCreation = `${index}-${indexDate(creation)}`; try { - await this.checkIndexAndCreateIfNotExists(indexCreation, shards, replicas); - const createDocumentObject = this.createDocument(doc, indexCreation, mapping); - const response = await this.esClientInternalUser.bulk(createDocumentObject); - log(this.logPath, `Response of create new document ${JSON.stringify(response)}`, 'debug'); - // await this.checkIndexPatternAndCreateIfNotExists(index); + await this.checkIndexAndCreateIfNotExists( + indexCreation, + shards, + replicas, + ); + const createDocumentObject = this.createDocument( + doc, + indexCreation, + mapping, + ); + this.context.wazuh.logger.debug('Bulk data'); + const response = await this.esClientInternalUser.bulk( + createDocumentObject, + ); + this.context.wazuh.logger.debug( + `Bulked data. Response of creating the new document ${JSON.stringify( + response, + )}`, + ); } catch (error) { - if (error.status === 403) - throw { error: 403, message: `Authorization Exception in the index "${index}"` } - if (error.status === 409) - throw { error: 409, message: `Duplicate index-pattern: ${index}` } + if (error.status === 403) { + throw { + error: 403, + message: `Authorization Exception in the index "${index}"`, + }; + } + if (error.status === 409) { + throw { error: 409, message: `Duplicate index-pattern: ${index}` }; + } throw error; } } private async checkIndexAndCreateIfNotExists(index, shards, replicas) { try { - await tryCatchForIndexPermissionError(index) (async() => { - const exists = await this.esClientInternalUser.indices.exists({ index }); - log(this.logPath, `Index '${index}' exists? ${exists.body}`, 'debug'); + await tryCatchForIndexPermissionError(index)(async () => { + this.context.wazuh.logger.debug( + `Checking the existence of ${index} index`, + ); + const exists = await this.esClientInternalUser.indices.exists({ + index, + }); + this.context.wazuh.logger.debug( + `Index '${index}' exists? ${exists.body}`, + ); if (!exists.body) { - const response = await this.esClientInternalUser.indices.create({ + this.context.wazuh.logger.debug(`Creating ${index} index`); + await this.esClientInternalUser.indices.create({ index, body: { settings: { index: { - number_of_shards: shards ?? WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, - number_of_replicas: replicas ?? WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS - } - } - } + number_of_shards: + shards ?? WAZUH_STATISTICS_DEFAULT_INDICES_SHARDS, + number_of_replicas: + replicas ?? WAZUH_STATISTICS_DEFAULT_INDICES_REPLICAS, + }, + }, + }, }); - log(this.logPath, `Status of create a new index: ${JSON.stringify(response)}`, 'debug'); + this.context.wazuh.logger.info(`${index} index created`); } })(); } catch (error) { @@ -68,21 +99,33 @@ export class SaveDocument { } private checkDuplicateIndexError(error: any) { - const { type } = ((error || {}).body || {}).error || {}; - if (!['resource_already_exists_exception'].includes(type)) + if ( + !['resource_already_exists_exception'].includes(error?.body?.error?.type) + ) { throw error; + } } - private createDocument(doc, index, mapping: string): BulkIndexDocumentsParams { + private createDocument( + doc, + index, + mapping: string, + ): BulkIndexDocumentsParams { const createDocumentObject: BulkIndexDocumentsParams = { index, - body: doc.map(item => `{"index": { "_index": "${index}" }}\n${JSON.stringify({ - ...this.buildData(item, mapping), - timestamp: new Date(Date.now()).toISOString() - })}\n`) - .join('') + body: doc + .map( + item => + `{"index": { "_index": "${index}" }}\n${JSON.stringify({ + ...this.buildData(item, mapping), + timestamp: new Date(Date.now()).toISOString(), + })}\n`, + ) + .join(''), }; - log(this.logPath, `Document object: ${JSON.stringify(createDocumentObject)}`, 'debug'); + this.context.wazuh.logger.debug( + `Document object: ${JSON.stringify(createDocumentObject)}`, + ); return createDocumentObject; } @@ -93,22 +136,21 @@ export class SaveDocument { const getValue = (key: string, item) => { const keys = key.split('.'); if (keys.length === 1) { - if(key.match(/\[.*\]/)){ + if (key.match(/\[.*\]/)) { return getItemArray( item[key.replace(/\[.*\]/, '')], - key.match(/\[(.*)\]/)[1] + key.match(/\[(.*)\]/)[1], ); } return JSON.stringify(item[key]); } - return getValue(keys.slice(1).join('.'), item[keys[0]]) - } + return getValue(keys.slice(1).join('.'), item[keys[0]]); + }; if (mapping) { let data; - data = mapping.replace( - /\${([a-z|A-Z|0-9|\.\-\_\[.*\]]+)}/gi, - (...key) => getValue(key[1], item) - ) + data = mapping.replace(/\${([a-z|A-Z|0-9|\.\-\_\[.*\]]+)}/gi, (...key) => + getValue(key[1], item), + ); return JSON.parse(data); } @@ -120,8 +162,8 @@ export class SaveDocument { private addIndexPrefix(index): string { const configFile = getConfiguration(); - const prefix = configFile['cron.prefix'] || 'wazuh'; + const prefix = + configFile['cron.prefix'] || getSettingDefaultValue('cron.prefix'); return `${prefix}-${index}`; } - } diff --git a/plugins/main/server/start/cron-scheduler/scheduler-handler.ts b/plugins/main/server/start/cron-scheduler/scheduler-handler.ts index 4da8234bca..a7f6134a02 100644 --- a/plugins/main/server/start/cron-scheduler/scheduler-handler.ts +++ b/plugins/main/server/start/cron-scheduler/scheduler-handler.ts @@ -1,6 +1,5 @@ import { jobs, SchedulerJob } from './index'; import { configuredJobs } from './configured-jobs'; -import { log } from '../../lib/logger'; import { getConfiguration } from '../../lib/get-configuration'; import cron from 'node-cron'; import { WAZUH_STATISTICS_TEMPLATE_NAME } from '../../../common/constants'; @@ -8,78 +7,70 @@ import { statisticsTemplate } from '../../integration-files/statistics-template' import { delayAsPromise } from '../../../common/utils'; import { getSettingDefaultValue } from '../../../common/services/settings'; -const blueWazuh = '\u001b[34mwazuh\u001b[39m'; -const schedulerErrorLogColors = [blueWazuh, 'scheduler', 'error']; const schedulerJobs = []; /** -* Wait until Kibana server is ready -*/ + * Wait until Kibana server is ready + */ const checkPluginPlatformStatus = async function (context) { try { - log( - 'scheduler-handler:checkPluginPlatformStatus', - 'Waiting for Kibana and Elasticsearch servers to be ready...', - 'debug' - ); + context.wazuh.logger.debug('Waiting for platform servers to be ready...'); await checkElasticsearchServer(context); await checkTemplate(context); return; } catch (error) { - log( - 'scheduler-handler:checkPluginPlatformStatus', - error.mesage ||error - ); - try{ - await delayAsPromise(3000); - await checkPluginPlatformStatus(context); - }catch(error){}; + context.wazuh.logger.warn(error.message || error); + try { + await delayAsPromise(3000); + await checkPluginPlatformStatus(context); + } catch (error) {} } - } - - - /** - * Check Elasticsearch Server status and Kibana index presence - */ - const checkElasticsearchServer = async function (context) { - try { - const data = await context.core.opensearch.client.asInternalUser.indices.exists({ - index: context.server.config.opensearchDashboards.index - }); +}; - return data.body; - } catch (error) { - log('scheduler-handler:checkElasticsearchServer', error.message || error); - return Promise.reject(error); - } - } +/** + * Check Elasticsearch Server status and Kibana index presence + */ +const checkElasticsearchServer = async function (context) { + context.wazuh.logger.debug( + `Checking the existence of ${context.server.config.opensearchDashboards.index} index`, + ); + const data = + await context.core.opensearch.client.asInternalUser.indices.exists({ + index: context.server.config.opensearchDashboards.index, + }); + return data.body; +}; - /** +/** * Verify wazuh-statistics template */ const checkTemplate = async function (context) { try { - log( - 'scheduler-handler:checkTemplate', - 'Updating the statistics template', - 'debug' - ); - const appConfig = await getConfiguration(); - const prefixTemplateName = appConfig['cron.prefix'] || getSettingDefaultValue('cron.prefix'); - const statisticsIndicesTemplateName = appConfig['cron.statistics.index.name'] || getSettingDefaultValue('cron.statistics.index.name'); + const prefixTemplateName = + appConfig['cron.prefix'] || getSettingDefaultValue('cron.prefix'); + const statisticsIndicesTemplateName = + appConfig['cron.statistics.index.name'] || + getSettingDefaultValue('cron.statistics.index.name'); const pattern = `${prefixTemplateName}-${statisticsIndicesTemplateName}-*`; try { // Check if the template already exists - const currentTemplate = await context.core.opensearch.client.asInternalUser.indices.getTemplate({ - name: WAZUH_STATISTICS_TEMPLATE_NAME - }); + context.wazuh.logger.debug( + `Getting the ${WAZUH_STATISTICS_TEMPLATE_NAME} template`, + ); + const currentTemplate = + await context.core.opensearch.client.asInternalUser.indices.getTemplate( + { + name: WAZUH_STATISTICS_TEMPLATE_NAME, + }, + ); // Copy already created index patterns - statisticsTemplate.index_patterns = currentTemplate.body[WAZUH_STATISTICS_TEMPLATE_NAME].index_patterns; - }catch (error) { + statisticsTemplate.index_patterns = + currentTemplate.body[WAZUH_STATISTICS_TEMPLATE_NAME].index_patterns; + } catch (error) { // Init with the default index pattern statisticsTemplate.index_patterns = [pattern]; } @@ -87,38 +78,35 @@ const checkTemplate = async function (context) { // Check if the user is using a custom pattern and add it to the template if it does if (!statisticsTemplate.index_patterns.includes(pattern)) { statisticsTemplate.index_patterns.push(pattern); - }; + } // Update the statistics template + context.wazuh.logger.debug( + `Updating the ${WAZUH_STATISTICS_TEMPLATE_NAME} template`, + ); await context.core.opensearch.client.asInternalUser.indices.putTemplate({ name: WAZUH_STATISTICS_TEMPLATE_NAME, - body: statisticsTemplate + body: statisticsTemplate, }); - log( - 'scheduler-handler:checkTemplate', - 'Updated the statistics template', - 'debug' + context.wazuh.logger.info( + `Updated the ${WAZUH_STATISTICS_TEMPLATE_NAME} template`, ); } catch (error) { - const errorMessage = `Something went wrong updating the statistics template ${error.message || error}`; - log( - 'scheduler-handler:checkTemplate', - errorMessage + context.wazuh.logger.error( + `Something went wrong updating the ${WAZUH_STATISTICS_TEMPLATE_NAME} template ${ + error.message || error + }`, ); - context.wazuh.logger.error(schedulerErrorLogColors, errorMessage); throw error; } -} +}; -export async function jobSchedulerRun(context){ +export async function jobSchedulerRun(context) { // Check Kibana index and if it is prepared, start the initialization of Wazuh App. await checkPluginPlatformStatus(context); for (const job in configuredJobs({})) { const schedulerJob: SchedulerJob = new SchedulerJob(job, context); schedulerJobs.push(schedulerJob); - const task = cron.schedule( - jobs[job].interval, - () => schedulerJob.run(), - ); + const task = cron.schedule(jobs[job].interval, () => schedulerJob.run()); } } diff --git a/plugins/main/server/start/cron-scheduler/scheduler-job.test.ts b/plugins/main/server/start/cron-scheduler/scheduler-job.test.ts index 51b5bec0db..4231c35007 100644 --- a/plugins/main/server/start/cron-scheduler/scheduler-job.test.ts +++ b/plugins/main/server/start/cron-scheduler/scheduler-job.test.ts @@ -1,6 +1,5 @@ //@ts-nocheck import { IApi, jobs, SchedulerJob } from './index'; -import { WazuhHostsCtrl } from '../../controllers/wazuh-hosts'; jest.mock('../../controllers/wazuh-hosts'); jest.mock('./save-document'); @@ -26,101 +25,100 @@ jest.mock('./predefined-jobs', () => ({ })); describe('SchedulerJob', () => { - const oneApi = { - body: [ - { - url: 'https://localhost', - port: 55000, - username: 'wazuh', - password: 'wazuh', - id: 'default', - cluster_info: { - status: 'disabled', - manager: 'master', - node: 'node01', - cluster: 'Disabled', - }, + const oneApi = [ + { + url: 'https://localhost', + port: 55000, + username: 'wazuh', + password: 'wazuh', + id: 'default', + cluster_info: { + status: 'disabled', + manager: 'master', + node: 'node01', + cluster: 'Disabled', }, - ], - }; - const twoApi = { - body: [ - { - url: 'https://localhost', - port: 55000, - username: 'wazuh', - password: 'wazuh', - id: 'internal', - cluster_info: { - status: 'disabled', - manager: 'master', - node: 'node01', - cluster: 'Disabled', - }, + }, + ]; + const twoApi = [ + { + url: 'https://localhost', + port: 55000, + username: 'wazuh', + password: 'wazuh', + id: 'internal', + cluster_info: { + status: 'disabled', + manager: 'master', + node: 'node01', + cluster: 'Disabled', }, - { - url: 'https://externalhost', - port: 55000, - username: 'wazuh', - password: 'wazuh', - id: 'external', - cluster_info: { - status: 'disabled', - manager: 'master', - node: 'node01', - cluster: 'Disabled', - }, + }, + { + url: 'https://externalhost', + port: 55000, + username: 'wazuh', + password: 'wazuh', + id: 'external', + cluster_info: { + status: 'disabled', + manager: 'master', + node: 'node01', + cluster: 'Disabled', }, - ], - }; - const threeApi = { - body: [ - { - url: 'https://localhost', - port: 55000, - username: 'wazuh', - password: 'wazuh', - id: 'internal', - cluster_info: { - status: 'disabled', - manager: 'master', - node: 'node01', - cluster: 'Disabled', - }, + }, + ]; + const threeApi = [ + { + url: 'https://localhost', + port: 55000, + username: 'wazuh', + password: 'wazuh', + id: 'internal', + cluster_info: { + status: 'disabled', + manager: 'master', + node: 'node01', + cluster: 'Disabled', }, - { - url: 'https://externalhost', - port: 55000, - username: 'wazuh', - password: 'wazuh', - id: 'external', - cluster_info: { - status: 'disabled', - manager: 'master', - node: 'node01', - cluster: 'Disabled', - }, + }, + { + url: 'https://externalhost', + port: 55000, + username: 'wazuh', + password: 'wazuh', + id: 'external', + cluster_info: { + status: 'disabled', + manager: 'master', + node: 'node01', + cluster: 'Disabled', }, - { - url: 'https://externalhost', - port: 55000, - username: 'wazuh', - password: 'wazuh', - id: 'experimental', - cluster_info: { - status: 'disabled', - manager: 'master', - node: 'node01', - cluster: 'Disabled', - }, + }, + { + url: 'https://externalhost', + port: 55000, + username: 'wazuh', + password: 'wazuh', + id: 'experimental', + cluster_info: { + status: 'disabled', + manager: 'master', + node: 'node01', + cluster: 'Disabled', }, - ], - }; + }, + ]; const mockContext = { wazuh: { logger: { logger: {} }, api: { client: [Object] }, }, + wazuh_core: { + serverAPIHostEntries: { + getHostsEntries: jest.fn(), + }, + }, }; let schedulerJob: SchedulerJob; @@ -139,28 +137,37 @@ describe('SchedulerJob', () => { }); it('should get API object when no specified the `apis` parameter on the job object', async () => { - WazuhHostsCtrl.prototype.getHostsEntries.mockResolvedValue(oneApi); + mockContext.wazuh_core.serverAPIHostEntries.getHostsEntries.mockResolvedValue( + oneApi, + ); const apis: IApi[] = await schedulerJob.getApiObjects(); expect(apis).not.toBeUndefined(); expect(apis).not.toBeFalsy(); - expect(apis).toEqual(oneApi.body); + expect(apis).toEqual(oneApi); }); it('should get all API objects when no specified the `apis` parameter on the job object', async () => { - WazuhHostsCtrl.prototype.getHostsEntries.mockResolvedValue(twoApi); + mockContext.wazuh_core.serverAPIHostEntries.getHostsEntries.mockResolvedValue( + twoApi, + ); const apis: IApi[] = await schedulerJob.getApiObjects(); expect(apis).not.toBeUndefined(); expect(apis).not.toBeFalsy(); - expect(apis).toEqual(twoApi.body); + expect(apis).toEqual(twoApi); }); it('should get one of two API object when specified the id in `apis` parameter on the job object', async () => { - WazuhHostsCtrl.prototype.getHostsEntries.mockResolvedValue(twoApi); - jobs[schedulerJob.jobName] = { ...jobs[schedulerJob.jobName], apis: ['internal'] }; + mockContext.wazuh_core.serverAPIHostEntries.getHostsEntries.mockResolvedValue( + twoApi, + ); + jobs[schedulerJob.jobName] = { + ...jobs[schedulerJob.jobName], + apis: ['internal'], + }; const apis: IApi[] = await schedulerJob.getApiObjects(); - const filteredTwoApi = twoApi.body.filter((item) => item.id === 'internal'); + const filteredTwoApi = twoApi.filter(item => item.id === 'internal'); expect(apis).not.toBeUndefined(); expect(apis).not.toBeFalsy(); @@ -168,11 +175,18 @@ describe('SchedulerJob', () => { }); it('should get two of three API object when specified the id in `apis` parameter on the job object', async () => { - WazuhHostsCtrl.prototype.getHostsEntries.mockResolvedValue(threeApi); + mockContext.wazuh_core.serverAPIHostEntries.getHostsEntries.mockResolvedValue( + threeApi, + ); const selectedApis = ['internal', 'external']; - jobs[schedulerJob.jobName] = { ...jobs[schedulerJob.jobName], apis: selectedApis }; + jobs[schedulerJob.jobName] = { + ...jobs[schedulerJob.jobName], + apis: selectedApis, + }; const apis: IApi[] = await schedulerJob.getApiObjects(); - const filteredThreeApi = threeApi.body.filter((item) => selectedApis.includes(item.id)); + const filteredThreeApi = threeApi.filter(item => + selectedApis.includes(item.id), + ); expect(apis).not.toBeUndefined(); expect(apis).not.toBeFalsy(); @@ -180,7 +194,9 @@ describe('SchedulerJob', () => { }); it('should throw an exception when no get APIs', async () => { - WazuhHostsCtrl.prototype.getHostsEntries.mockResolvedValue({ body: [] }); + mockContext.wazuh_core.serverAPIHostEntries.getHostsEntries.mockResolvedValue( + [], + ); await expect(schedulerJob.getApiObjects()).rejects.toEqual({ error: 10001, message: 'No Wazuh host configured in wazuh.yml', @@ -188,8 +204,13 @@ describe('SchedulerJob', () => { }); it('should throw an exception when no match API', async () => { - WazuhHostsCtrl.prototype.getHostsEntries.mockResolvedValue(threeApi); - jobs[schedulerJob.jobName] = { ...jobs[schedulerJob.jobName], apis: ['unkown'] }; + mockContext.wazuh_core.serverAPIHostEntries.getHostsEntries.mockResolvedValue( + threeApi, + ); + jobs[schedulerJob.jobName] = { + ...jobs[schedulerJob.jobName], + apis: ['unkown'], + }; await expect(schedulerJob.getApiObjects()).rejects.toEqual({ error: 10002, message: 'No host was found with the indicated ID', diff --git a/plugins/main/server/start/cron-scheduler/scheduler-job.ts b/plugins/main/server/start/cron-scheduler/scheduler-job.ts index f64561dd5d..99b17ee3f7 100644 --- a/plugins/main/server/start/cron-scheduler/scheduler-job.ts +++ b/plugins/main/server/start/cron-scheduler/scheduler-job.ts @@ -1,15 +1,8 @@ import { jobs } from './predefined-jobs'; -import { WazuhHostsCtrl } from '../../controllers/wazuh-hosts'; import { IApi, SaveDocument } from './index'; import { ErrorHandler } from './error-handler'; import { configuredJobs } from './configured-jobs'; -const wazuhHostsController = new WazuhHostsCtrl(); - -const fakeResponseEndpoint = { - ok: (body: any) => body, - custom: (body: any) => body, -}; export class SchedulerJob { jobName: string; saveDocument: SaveDocument; @@ -27,20 +20,28 @@ export class SchedulerJob { public async run() { const { index, status } = configuredJobs({})[this.jobName]; - if (!status) { return; } + if (!status) { + return; + } try { const hosts = await this.getApiObjects(); const jobPromises = hosts.map(async host => { try { - const { status } = configuredJobs({ host, jobName: this.jobName })[this.jobName]; + const { status } = configuredJobs({ host, jobName: this.jobName })[ + this.jobName + ]; if (!status) return; return await this.getResponses(host); } catch (error) { ErrorHandler(error, this.logger); } }); - const data = (await Promise.all(jobPromises)).filter(promise => !!promise).flat(); - Array.isArray(data) && !!data.length && await this.saveDocument.save(data, index); + const data = (await Promise.all(jobPromises)) + .filter(promise => !!promise) + .flat(); + Array.isArray(data) && + !!data.length && + (await this.saveDocument.save(data, index)); } catch (error) { ErrorHandler(error, this.logger); } @@ -48,30 +49,37 @@ export class SchedulerJob { private async getApiObjects() { const { apis } = jobs[this.jobName]; - const hostsResponse: {body: IApi[]} = await wazuhHostsController.getHostsEntries(false, false, fakeResponseEndpoint); - if (!hostsResponse.body.length) throw {error: 10001, message: 'No Wazuh host configured in wazuh.yml' } - if(apis && apis.length){ - return this.filterHosts(hostsResponse.body, apis); + const hostsResponse: IApi[] = + await this.context.wazuh_core.serverAPIHostEntries.getHostsEntries(); + if (!hostsResponse.length) + throw { error: 10001, message: 'No Wazuh host configured in wazuh.yml' }; + if (apis && apis.length) { + return this.filterHosts(hostsResponse, apis); } - return hostsResponse.body; + return hostsResponse; } private filterHosts(hosts: IApi[], apis: string[]) { const filteredHosts = hosts.filter(host => apis.includes(host.id)); if (filteredHosts.length <= 0) { - throw {error: 10002, message: 'No host was found with the indicated ID'}; + throw { + error: 10002, + message: 'No host was found with the indicated ID', + }; } return filteredHosts; } private async getResponses(host): Promise { const { request, params } = jobs[this.jobName]; - const data:object[] = []; - + const data: object[] = []; + if (typeof request === 'string') { - const apiResponse = await this.apiClient.request('GET', request, params, { apiHostID: host.id }); - data.push({...apiResponse.data, apiName:host.id}); - }else { + const apiResponse = await this.apiClient.request('GET', request, params, { + apiHostID: host.id, + }); + data.push({ ...apiResponse.data, apiName: host.id }); + } else { await this.getResponsesForIRequest(host, data); } return data; @@ -79,47 +87,77 @@ export class SchedulerJob { private async getResponsesForIRequest(host: any, data: object[]) { const { request, params } = jobs[this.jobName]; - const fieldName = this.getParamName(typeof request !== 'string' && request.request); + const fieldName = this.getParamName( + typeof request !== 'string' && request.request, + ); const paramList = await this.getParamList(fieldName, host); for (const param of paramList) { - const paramRequest = typeof request !== 'string' && request.request.replace(/\{.+\}/, param); - if(!!paramRequest){ - const apiResponse = await this.apiClient.request('GET', paramRequest, params, { apiHostID: host.id }); + const paramRequest = + typeof request !== 'string' && request.request.replace(/\{.+\}/, param); + if (!!paramRequest) { + const apiResponse = await this.apiClient.request( + 'GET', + paramRequest, + params, + { apiHostID: host.id }, + ); data.push({ ...apiResponse.data, apiName: host.id, [fieldName]: param, }); } - } } private getParamName(request): string { const regexResult = /\{(?.+)\}/.exec(request); - if (regexResult === null) throw {error: 10003, message: `The parameter is not found in the Request: ${request}`}; + if (regexResult === null) + throw { + error: 10003, + message: `The parameter is not found in the Request: ${request}`, + }; // @ts-ignore const { fieldName } = regexResult.groups; - if (fieldName === undefined || fieldName === '') throw {error: 10004, message: `Invalid field in the request: {request: ${request}, field: ${fieldName}}`} - return fieldName + if (fieldName === undefined || fieldName === '') + throw { + error: 10004, + message: `Invalid field in the request: {request: ${request}, field: ${fieldName}}`, + }; + return fieldName; } private async getParamList(fieldName, host) { const { request } = jobs[this.jobName]; // @ts-ignore - const apiResponse = await this.apiClient.request('GET', request.params[fieldName].request, {}, { apiHostID: host.id }); + const apiResponse = await this.apiClient.request( + 'GET', + request.params[fieldName].request, + {}, + { apiHostID: host.id }, + ); const { affected_items } = apiResponse.data.data; - if (affected_items === undefined || affected_items.length === 0 ) throw {error: 10005, message: `Empty response when tried to get the parameters list: ${JSON.stringify(apiResponse.data)}`} - const values = affected_items.map(this.mapParamList) - return values + if (affected_items === undefined || affected_items.length === 0) + throw { + error: 10005, + message: `Empty response when tried to get the parameters list: ${JSON.stringify( + apiResponse.data, + )}`, + }; + const values = affected_items.map(this.mapParamList); + return values; } private mapParamList(item) { if (typeof item !== 'object') { - return item - }; - const keys = Object.keys(item) - if(keys.length > 1 || keys.length < 0) throw { error: 10006, message: `More than one key or none were obtained: ${keys}`} + return item; + } + const keys = Object.keys(item); + if (keys.length > 1 || keys.length < 0) + throw { + error: 10006, + message: `More than one key or none were obtained: ${keys}`, + }; return item[keys[0]]; } } diff --git a/plugins/main/server/start/initialize/index.test.ts b/plugins/main/server/start/initialize/index.test.ts index a716c8235e..386f750a50 100644 --- a/plugins/main/server/start/initialize/index.test.ts +++ b/plugins/main/server/start/initialize/index.test.ts @@ -62,10 +62,6 @@ function mockContextCreator(loggerLevel: string) { return ctx; } -jest.mock('../../lib/logger', () => ({ - log: jest.fn(), -})); - jest.mock('../../lib/get-configuration', () => ({ getConfiguration: () => ({ pattern: 'wazuh-alerts-*' }), })); diff --git a/plugins/main/server/start/initialize/index.ts b/plugins/main/server/start/initialize/index.ts index e4514b0ab1..a90df44dc5 100644 --- a/plugins/main/server/start/initialize/index.ts +++ b/plugins/main/server/start/initialize/index.ts @@ -9,10 +9,8 @@ * * Find more information about this on the LICENSE file. */ -import { log } from '../../lib/logger'; import packageJSON from '../../../package.json'; import { pluginPlatformTemplate } from '../../integration-files/kibana-template'; -import { getConfiguration } from '../../lib/get-configuration'; import { totalmem } from 'os'; import fs from 'fs'; import { @@ -22,52 +20,27 @@ import { PLUGIN_PLATFORM_NAME, PLUGIN_PLATFORM_INSTALLATION_USER_GROUP, PLUGIN_PLATFORM_INSTALLATION_USER, - WAZUH_DEFAULT_APP_CONFIG, PLUGIN_APP_NAME, } from '../../../common/constants'; import { createDataDirectoryIfNotExists } from '../../lib/filesystem'; import _ from 'lodash'; -import { - getSettingDefaultValue, - getSettingsDefault, -} from '../../../common/services/settings'; export function jobInitializeRun(context) { const PLUGIN_PLATFORM_INDEX = context.server.config.opensearchDashboards.index; - log( - 'initialize', + context.wazuh.logger.info( `${PLUGIN_PLATFORM_NAME} index: ${PLUGIN_PLATFORM_INDEX}`, - 'info', ); - log('initialize', `App revision: ${packageJSON.revision}`, 'info'); - - let configurationFile = {}; - let pattern = null; - // Read config from package.json and wazuh.yml - try { - configurationFile = getConfiguration(); - - pattern = - configurationFile && typeof configurationFile.pattern !== 'undefined' - ? configurationFile.pattern - : getSettingDefaultValue('pattern'); - } catch (error) { - log('initialize', error.message || error); - context.wazuh.logger.error( - 'Something went wrong while reading the configuration.' + - (error.message || error), - ); - } + context.wazuh.logger.info(`App revision: ${packageJSON.revision}`); try { // RAM in MB + context.wazuh.logger.debug('Getting the total RAM memory'); const ram = Math.ceil(totalmem() / 1024 / 1024); - log('initialize', `Total RAM: ${ram}MB`, 'info'); + context.wazuh.logger.info(`Total RAM: ${ram}MB`); } catch (error) { - log( - 'initialize', - `Could not check total RAM due to: ${error.message || error}`, + context.wazuh.logger.error( + `Could not check total RAM due to: ${error.message}`, ); } @@ -75,7 +48,6 @@ export function jobInitializeRun(context) { const saveConfiguration = async (hosts = {}) => { try { const commonDate = new Date().toISOString(); - const configuration = { name: PLUGIN_APP_NAME, 'app-version': packageJSON.version, @@ -84,35 +56,24 @@ export function jobInitializeRun(context) { lastRestart: commonDate, hosts, }; - try { - createDataDirectoryIfNotExists(); - createDataDirectoryIfNotExists('config'); - log( - 'initialize:saveConfiguration', - `Saving configuration in registry file: ${JSON.stringify( - configuration, - )}`, - 'debug', - ); - await fs.writeFileSync( - WAZUH_DATA_CONFIG_REGISTRY_PATH, - JSON.stringify(configuration), - 'utf8', - ); - log( - 'initialize:saveConfiguration', - 'Wazuh configuration registry saved.', - 'debug', - ); - } catch (error) { - log('initialize:saveConfiguration', error.message || error); - context.wazuh.logger.error( - 'Could not create Wazuh configuration registry', - ); - } + context.wazuh.logger.debug('Saving the configuration'); + createDataDirectoryIfNotExists(); + createDataDirectoryIfNotExists('config'); + context.wazuh.logger.debug( + `Saving configuration in registry file: ${JSON.stringify( + configuration, + )}`, + ); + await fs.writeFileSync( + WAZUH_DATA_CONFIG_REGISTRY_PATH, + JSON.stringify(configuration), + 'utf8', + ); + context.wazuh.logger.info('Configuration registry saved.'); } catch (error) { - log('initialize:saveConfiguration', error.message || error); - context.wazuh.logger.error('Error creating wazuh-registry.json file.'); + context.wazuh.logger.error( + `Error creating the registry file: ${error.message}`, + ); } }; @@ -123,11 +84,7 @@ export function jobInitializeRun(context) { * - no: create the file with empty hosts */ const checkWazuhRegistry = async () => { - log( - 'initialize:checkwazuhRegistry', - 'Checking wazuh-registry.json file.', - 'debug', - ); + context.wazuh.logger.debug('Checking the existence app data directory.'); if (!fs.existsSync(WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH)) { throw new Error( @@ -135,20 +92,22 @@ export function jobInitializeRun(context) { ); } + context.wazuh.logger.debug('Checking the existence of registry file.'); + if (!fs.existsSync(WAZUH_DATA_CONFIG_REGISTRY_PATH)) { - log( - 'initialize:checkwazuhRegistry', - 'wazuh-registry.json file does not exist. Initializing configuration.', - 'debug', + context.wazuh.logger.debug( + 'Registry file does not exist. Initializing configuration.', ); // Create the app registry file for the very first time await saveConfiguration(); } else { + context.wazuh.logger.debug('Reading the registry file'); // If this function fails, it throws an exception const source = JSON.parse( fs.readFileSync(WAZUH_DATA_CONFIG_REGISTRY_PATH, 'utf8'), ); + context.wazuh.logger.debug('The registry file was read'); // Check if the stored revision differs from the package.json revision const isUpgradedApp = @@ -157,11 +116,13 @@ export function jobInitializeRun(context) { // Rebuild the registry file if revision or version fields are differents if (isUpgradedApp) { - // Generate the hosts data + context.wazuh.logger.info( + 'App revision or version changed, regenerating registry file', + ); + // Generate the hosts data. const registryHostsData = Object.entries(source.hosts).reduce( (accum, [hostID, hostData]) => { - // We have removed the 'extensions' property from the host as module - // logic has been eliminated, so this is a migration process. + // Migration: Remove the extensions property of the hosts data. if (hostData.extensions) { delete hostData.extensions; } @@ -171,21 +132,10 @@ export function jobInitializeRun(context) { {}, ); - log( - 'initialize:checkwazuhRegistry', - 'Wazuh app revision or version changed, regenerating wazuh-registry.json.', - 'info', - ); - - // Rebuild the registry file with the migrated host data (extensions are - // migrated to these supported by the installed plugin). + // Rebuild the registry file with the migrated host data await saveConfiguration(registryHostsData); - log( - 'initialize:checkwazuhRegistry', - 'Migrated the registry file.', - 'info', - ); + context.wazuh.logger.info('Migrated the registry file.'); } } }; @@ -196,17 +146,14 @@ export function jobInitializeRun(context) { }; const createKibanaTemplate = () => { - log( - 'initialize:createKibanaTemplate', + context.wazuh.logger.debug( `Creating template for ${PLUGIN_PLATFORM_INDEX}`, - 'debug', ); try { pluginPlatformTemplate.template = PLUGIN_PLATFORM_INDEX + '*'; } catch (error) { - log('initialize:createKibanaTemplate', error.message || error); - context.wazuh.logger.error('Exception: ' + error.message || error); + context.wazuh.logger.error('Exception: ' + error.message); } return context.core.opensearch.client.asInternalUser.indices.putTemplate({ @@ -219,64 +166,46 @@ export function jobInitializeRun(context) { const createEmptyKibanaIndex = async () => { try { - log( - 'initialize:createEmptyKibanaIndex', - `Creating ${PLUGIN_PLATFORM_INDEX} index.`, - 'info', - ); + context.wazuh.logger.debug(`Creating ${PLUGIN_PLATFORM_INDEX} index.`); await context.core.opensearch.client.asInternalUser.indices.create({ index: PLUGIN_PLATFORM_INDEX, }); - log( - 'initialize:createEmptyKibanaIndex', - `Successfully created ${PLUGIN_PLATFORM_INDEX} index.`, - 'debug', - ); + context.wazuh.logger.info(`${PLUGIN_PLATFORM_INDEX} index created`); await init(); } catch (error) { - return Promise.reject( - new Error( - `Error creating ${PLUGIN_PLATFORM_INDEX} index due to ${ - error.message || error - }`, - ), + throw new Error( + `Error creating ${PLUGIN_PLATFORM_INDEX} index: ${error.message}`, ); } }; const fixKibanaTemplate = async () => { try { + context.wazuh.logger.debug(`Fixing ${PLUGIN_PLATFORM_INDEX} template`); await createKibanaTemplate(); - log( - 'initialize:fixKibanaTemplate', - `Successfully created ${PLUGIN_PLATFORM_INDEX} template.`, - 'debug', - ); + context.wazuh.logger.info(`${PLUGIN_PLATFORM_INDEX} template created`); await createEmptyKibanaIndex(); } catch (error) { - return Promise.reject( - new Error( - `Error creating template for ${PLUGIN_PLATFORM_INDEX} due to ${ - error.message || error - }`, - ), + throw new Error( + `Error creating template for ${PLUGIN_PLATFORM_INDEX}: ${error.message}`, ); } }; const getTemplateByName = async () => { try { + context.wazuh.logger.debug( + `Getting ${WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME} template`, + ); await context.core.opensearch.client.asInternalUser.indices.getTemplate({ name: WAZUH_PLUGIN_PLATFORM_TEMPLATE_NAME, }); - log( - 'initialize:getTemplateByName', + context.wazuh.logger.debug( `No need to create the ${PLUGIN_PLATFORM_INDEX} template, already exists.`, - 'debug', ); await createEmptyKibanaIndex(); } catch (error) { - log('initialize:getTemplateByName', error.message || error); + context.wazuh.logger.warn(error.message || error); return fixKibanaTemplate(); } }; @@ -284,24 +213,26 @@ export function jobInitializeRun(context) { // Does Kibana index exist? const checkKibanaStatus = async () => { try { + context.wazuh.logger.debug( + `Checking the existence of ${PLUGIN_PLATFORM_INDEX} index`, + ); const response = await context.core.opensearch.client.asInternalUser.indices.exists({ index: PLUGIN_PLATFORM_INDEX, }); if (response.body) { + context.wazuh.logger.debug(`${PLUGIN_PLATFORM_INDEX} index exist`); // It exists, initialize! await init(); } else { - // No Kibana index created... - log( - 'initialize:checkKibanaStatus', - `Not found ${PLUGIN_PLATFORM_INDEX} index`, - 'info', + context.wazuh.logger.debug( + `${PLUGIN_PLATFORM_INDEX} index does not exist`, ); + // No Kibana index created... + context.wazuh.logger.info(`${PLUGIN_PLATFORM_INDEX} index not found`); await getTemplateByName(); } } catch (error) { - log('initialize:checkKibanaStatus', error.message || error); context.wazuh.logger.error(error.message || error); } }; @@ -313,10 +244,8 @@ export function jobInitializeRun(context) { // await server.plugins.opensearch.waitUntilReady(); return await checkKibanaStatus(); } catch (error) { - log( - 'initialize:checkStatus', + context.wazuh.logger.debug( 'Waiting for opensearch plugin to be ready...', - 'debug', ); setTimeout(() => checkStatus(), 3000); } diff --git a/plugins/main/server/start/migration-tasks/index.ts b/plugins/main/server/start/migration-tasks/index.ts index 025751c0cc..82ecf2f19c 100644 --- a/plugins/main/server/start/migration-tasks/index.ts +++ b/plugins/main/server/start/migration-tasks/index.ts @@ -1,9 +1,8 @@ -import migrateReportsDirectoryName from "./reports_directory_name"; +import migrateReportsDirectoryName from './reports_directory_name'; export function jobMigrationTasksRun(context) { - const migrationTasks = [ - migrateReportsDirectoryName - ]; + context.wazuh.logger.debug('Migration tasks started'); + const migrationTasks = [migrateReportsDirectoryName]; migrationTasks.forEach(task => task(context)); -} \ No newline at end of file +} diff --git a/plugins/main/server/start/migration-tasks/reports_directory_name.test.ts b/plugins/main/server/start/migration-tasks/reports_directory_name.test.ts index 3cb71073b3..226fe4123e 100644 --- a/plugins/main/server/start/migration-tasks/reports_directory_name.test.ts +++ b/plugins/main/server/start/migration-tasks/reports_directory_name.test.ts @@ -2,8 +2,15 @@ import fs from 'fs'; import md5 from 'md5'; import { execSync } from 'child_process'; import path from 'path'; -import { WAZUH_DATA_ABSOLUTE_PATH, WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH } from '../../../common/constants'; -import { createDataDirectoryIfNotExists, createDirectoryIfNotExists } from '../../lib/filesystem'; +import { + WAZUH_DATA_ABSOLUTE_PATH, + WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, +} from '../../../common/constants'; +import { + createDataDirectoryIfNotExists, + createDirectoryIfNotExists, +} from '../../lib/filesystem'; import migrateReportsDirectoryName, { isMD5 } from './reports_directory_name'; function mockContextCreator(loggerLevel: string) { @@ -12,12 +19,14 @@ function mockContextCreator(loggerLevel: string) { function createLogger(level: string) { return jest.fn(function (message: string) { - const levelLogIncluded: number = levels.findIndex((level) => level === loggerLevel); - levelLogIncluded > -1 - && levels.slice(levelLogIncluded).includes(level) - && logs.push({ level, message }); + const levelLogIncluded: number = levels.findIndex( + level => level === loggerLevel, + ); + levelLogIncluded > -1 && + levels.slice(levelLogIncluded).includes(level) && + logs.push({ level, message }); }); - }; + } const ctx = { wazuh: { @@ -25,20 +34,16 @@ function mockContextCreator(loggerLevel: string) { info: createLogger('info'), warn: createLogger('warn'), error: createLogger('error'), - debug: createLogger('debug') - } + debug: createLogger('debug'), + }, }, /* Mocked logs getter. It is only for testing purpose.*/ _getLogs(logLevel: string) { return logLevel ? logs.filter(({ level }) => level === logLevel) : logs; - } - } + }, + }; return ctx; -}; - -jest.mock('../../lib/logger', () => ({ - log: jest.fn() -})); +} beforeAll(() => { // Create /data/wazuh directory. @@ -59,10 +64,21 @@ describe("[migration] `reports` directory doesn't exist", () => { // Migrate the directories migrateReportsDirectoryName(mockContext); // Logs that the task started and skipped. - expect(mockContext._getLogs('debug').filter(({ message }) => message.includes("Task started"))).toHaveLength(1); - expect(mockContext._getLogs('debug').filter(({ message }) => message.includes("Reports directory doesn't exist. The task is not required. Skip."))).toHaveLength(1); + expect( + mockContext + ._getLogs('debug') + .filter(({ message }) => message.includes('Task started')), + ).toHaveLength(1); + expect( + mockContext + ._getLogs('debug') + .filter(({ message }) => + message.includes( + "Reports directory doesn't exist. The task is not required. Skip.", + ), + ), + ).toHaveLength(1); }); - }); describe('[migration] Rename the subdirectories of `reports` directory', () => { @@ -91,72 +107,178 @@ describe('[migration] Rename the subdirectories of `reports` directory', () => { const userDirectoriesTest1 = []; const userDirectoriesTest2 = [userNameDirectory1, userNameDirectory2]; - const userDirectoriesTest3 = [userNameDirectory1, userNameDirectory2, userNameDirectory3, userNameDirectory4]; + const userDirectoriesTest3 = [ + userNameDirectory1, + userNameDirectory2, + userNameDirectory3, + userNameDirectory4, + ]; const userDirectoriesTest4 = [userNameDirectory1, userNameDirectory1MD5]; - const userDirectoriesTest5 = [{ ...userNameDirectory1, errorRenaming: true }, userNameDirectory1MD5WithFiles, userNameDirectory2]; - const userDirectoriesTest6 = [{ ...userNameDirectory1, errorRenaming: true }, userNameDirectory1MD5WithFiles, { ...userNameDirectory2, errorRenaming: true }, userNameDirectory2MD5WithFiles]; - const userDirectoriesTest7 = [userNameDirectory1WithFiles, userNameDirectory2WithFiles]; - const userDirectoriesTest8 = [userNameDirectory1MD5WithFiles, userNameDirectory2MD5WithFiles]; + const userDirectoriesTest5 = [ + { ...userNameDirectory1, errorRenaming: true }, + userNameDirectory1MD5WithFiles, + userNameDirectory2, + ]; + const userDirectoriesTest6 = [ + { ...userNameDirectory1, errorRenaming: true }, + userNameDirectory1MD5WithFiles, + { ...userNameDirectory2, errorRenaming: true }, + userNameDirectory2MD5WithFiles, + ]; + const userDirectoriesTest7 = [ + userNameDirectory1WithFiles, + userNameDirectory2WithFiles, + ]; + const userDirectoriesTest8 = [ + userNameDirectory1MD5WithFiles, + userNameDirectory2MD5WithFiles, + ]; function formatUserDirectoriesTest(inputs: any) { return inputs.length - ? inputs.map(input => `[${input.name}:${input.files}${input.errorRenaming ? ' (Error: renaming)' : ''}]`).join(', ') - : 'None' - }; + ? inputs + .map( + input => + `[${input.name}:${input.files}${ + input.errorRenaming ? ' (Error: renaming)' : '' + }]`, + ) + .join(', ') + : 'None'; + } it.each` - directories | foundRequireRenamingDirectories | renamedDirectories | title - ${userDirectoriesTest1} | ${0} | ${0} | ${formatUserDirectoriesTest(userDirectoriesTest1)} - ${userDirectoriesTest2} | ${2} | ${2} | ${formatUserDirectoriesTest(userDirectoriesTest2)} - ${userDirectoriesTest3} | ${4} | ${4} | ${formatUserDirectoriesTest(userDirectoriesTest3)} - ${userDirectoriesTest4} | ${1} | ${1} | ${formatUserDirectoriesTest(userDirectoriesTest4)} - ${userDirectoriesTest5} | ${2} | ${1} | ${formatUserDirectoriesTest(userDirectoriesTest5)} - ${userDirectoriesTest6} | ${2} | ${0} | ${formatUserDirectoriesTest(userDirectoriesTest6)} - ${userDirectoriesTest7} | ${2} | ${2} | ${formatUserDirectoriesTest(userDirectoriesTest7)} - ${userDirectoriesTest8} | ${0} | ${0} | ${formatUserDirectoriesTest(userDirectoriesTest8)} - `('Migrate Directories: $title - FoundRequireRenamingDirectories: $foundRequireRenamingDirectories - renamedDirectories: $renamedDirectories.', ({ directories, foundRequireRenamingDirectories, renamedDirectories }) => { - - const errorRenamingDirectoryMessages = foundRequireRenamingDirectories - renamedDirectories; - // Create directories and file/s within directory. - directories.forEach(({ name, files }) => { - createDirectoryIfNotExists(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name)); - if (files) { - Array.from(Array(files).keys()).forEach(indexFile => { - fs.closeSync(fs.openSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name, `report_${indexFile}.pdf`), 'w')); - }); - } - }); + directories | foundRequireRenamingDirectories | renamedDirectories | title + ${userDirectoriesTest1} | ${0} | ${0} | ${formatUserDirectoriesTest(userDirectoriesTest1)} + ${userDirectoriesTest2} | ${2} | ${2} | ${formatUserDirectoriesTest(userDirectoriesTest2)} + ${userDirectoriesTest3} | ${4} | ${4} | ${formatUserDirectoriesTest(userDirectoriesTest3)} + ${userDirectoriesTest4} | ${1} | ${1} | ${formatUserDirectoriesTest(userDirectoriesTest4)} + ${userDirectoriesTest5} | ${2} | ${1} | ${formatUserDirectoriesTest(userDirectoriesTest5)} + ${userDirectoriesTest6} | ${2} | ${0} | ${formatUserDirectoriesTest(userDirectoriesTest6)} + ${userDirectoriesTest7} | ${2} | ${2} | ${formatUserDirectoriesTest(userDirectoriesTest7)} + ${userDirectoriesTest8} | ${0} | ${0} | ${formatUserDirectoriesTest(userDirectoriesTest8)} + `( + 'Migrate Directories: $title - FoundRequireRenamingDirectories: $foundRequireRenamingDirectories - renamedDirectories: $renamedDirectories.', + ({ directories, foundRequireRenamingDirectories, renamedDirectories }) => { + const errorRenamingDirectoryMessages = + foundRequireRenamingDirectories - renamedDirectories; + // Create directories and file/s within directory. + directories.forEach(({ name, files }) => { + createDirectoryIfNotExists( + path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name), + ); + if (files) { + Array.from(Array(files).keys()).forEach(indexFile => { + fs.closeSync( + fs.openSync( + path.join( + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, + name, + `report_${indexFile}.pdf`, + ), + 'w', + ), + ); + }); + } + }); - // Migrate the directories. - migrateReportsDirectoryName(mockContext); + // Migrate the directories. + migrateReportsDirectoryName(mockContext); + + // Check the quantity of directories were found for renaming renaming. + expect( + mockContext + ._getLogs() + .filter(({ message }) => + message.includes('Found reports directory to migrate'), + ), + ).toHaveLength(foundRequireRenamingDirectories); + // Check the quantity of directories were renamed. + expect( + mockContext + ._getLogs() + .filter(({ message }) => message.includes('Renamed directory [')), + ).toHaveLength(renamedDirectories); + expect( + mockContext + ._getLogs('error') + .filter(({ message }) => + message.includes(`Error renaming directory [`), + ), + ).toHaveLength(errorRenamingDirectoryMessages); - // Check the quantity of directories were found for renaming renaming. - expect(mockContext._getLogs().filter(({ message }) => message.includes('Found reports directory to migrate'))).toHaveLength(foundRequireRenamingDirectories); - // Check the quantity of directories were renamed. - expect(mockContext._getLogs().filter(({ message }) => message.includes('Renamed directory ['))).toHaveLength(renamedDirectories); - expect(mockContext._getLogs('error').filter(({ message }) => message.includes(`Error renaming directory [`))).toHaveLength(errorRenamingDirectoryMessages); - - directories.forEach(({ name, ...rest }) => { - if (!rest.errorRenaming) { - if (isMD5(name)) { - // If directory name is a valid MD5, the directory should exist. - expect(fs.existsSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name))).toBe(true); + directories.forEach(({ name, ...rest }) => { + if (!rest.errorRenaming) { + if (isMD5(name)) { + // If directory name is a valid MD5, the directory should exist. + expect( + fs.existsSync( + path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name), + ), + ).toBe(true); + } else { + // If directory name is not a valid MD5, the directory should be renamed. New directory exists and old directory doesn't exist. + expect( + mockContext + ._getLogs() + .filter(({ message }) => + message.includes(`Renamed directory [${name}`), + ), + ).toHaveLength(1); + expect( + mockContext + ._getLogs() + .filter(({ message }) => + message.includes( + `Found reports directory to migrate: [${name}`, + ), + ), + ).toHaveLength(1); + expect( + fs.existsSync( + path.join( + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, + md5(name), + ), + ), + ).toBe(true); + expect( + !fs.existsSync( + path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name), + ), + ).toBe(true); + } } else { - // If directory name is not a valid MD5, the directory should be renamed. New directory exists and old directory doesn't exist. - expect(mockContext._getLogs().filter(({ message }) => message.includes(`Renamed directory [${name}`))).toHaveLength(1); - expect(mockContext._getLogs().filter(({ message }) => message.includes(`Found reports directory to migrate: [${name}`))).toHaveLength(1); - expect(fs.existsSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, md5(name)))).toBe(true); - expect(!fs.existsSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name))).toBe(true); - }; - } else { - // Check there was an error renaming the directory because of the directory exist and contains files. - expect(mockContext._getLogs().filter(({ message }) => message.includes(`Found reports directory to migrate: [${name}`))).toHaveLength(1); - expect( - mockContext._getLogs('error').some(({ message }) => message.includes(`Error renaming directory [${name}`)) - ).toBe(true); - expect(fs.existsSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name))).toBe(true); - expect(fs.existsSync(path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, md5(name)))).toBe(true); - } - }); - }); + // Check there was an error renaming the directory because of the directory exist and contains files. + expect( + mockContext + ._getLogs() + .filter(({ message }) => + message.includes( + `Found reports directory to migrate: [${name}`, + ), + ), + ).toHaveLength(1); + expect( + mockContext + ._getLogs('error') + .some(({ message }) => + message.includes(`Error renaming directory [${name}`), + ), + ).toBe(true); + expect( + fs.existsSync( + path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, name), + ), + ).toBe(true); + expect( + fs.existsSync( + path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, md5(name)), + ), + ).toBe(true); + } + }); + }, + ); }); diff --git a/plugins/main/server/start/migration-tasks/reports_directory_name.ts b/plugins/main/server/start/migration-tasks/reports_directory_name.ts index df81b8851e..288f8524f2 100644 --- a/plugins/main/server/start/migration-tasks/reports_directory_name.ts +++ b/plugins/main/server/start/migration-tasks/reports_directory_name.ts @@ -2,18 +2,15 @@ import fs from 'fs'; import md5 from 'md5'; import path from 'path'; import { WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH } from '../../../common/constants'; -import { log } from '../../lib/logger'; /** * This task renames the report user folder from username to hashed username. - * @param context - * @returns + * @param context + * @returns */ export default function migrateReportsDirectoryName(context) { - // Create a wrapper function that logs to plugin files and platform logging system - const createLog = (level: string) => (message) => { - log('migration:reportsDirectoryName', message, level); + const createLog = (level: string) => message => { context.wazuh.logger[level](`migration:reportsDirectoryName: ${message}`); }; @@ -26,38 +23,55 @@ export default function migrateReportsDirectoryName(context) { }; try { - logger.debug('Task started'); + logger.debug('Task started: Migrate reports directory name'); // Skip the task if the directory that stores the reports files doesn't exist in the file system if (!fs.existsSync(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH)) { - logger.debug("Reports directory doesn't exist. The task is not required. Skip."); + logger.debug( + "Reports directory doesn't exist. The task is not required. Skip.", + ); return; - }; + } // Read the directories/files in the reports path - logger.debug(`Reading reports directory: ${WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH}`); - fs.readdirSync(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, { withFileTypes: true }) - .forEach((fileDirent) => { - // If it is a directory and has not a valid MD5 hash, continue the task. - if (fileDirent.isDirectory() && !isMD5(fileDirent.name)) { - // Generate the origin and target path and hash the name - const originDirectoryPath = path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, fileDirent.name); - const targetDirectoryName = md5(fileDirent.name); - const targetDirectoryPath = path.join(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, targetDirectoryName); - try { - logger.info(`Found reports directory to migrate: [${fileDirent.name}]`); - // Rename the directory from origin to target path - fs.renameSync(originDirectoryPath, targetDirectoryPath); - logger.info(`Renamed directory [${fileDirent.name} (${originDirectoryPath})] to [${targetDirectoryName} (${targetDirectoryPath})]`); - } catch (error) { - logger.error(`Error renaming directory [${fileDirent.name} (${originDirectoryPath})] to [${targetDirectoryName} (${targetDirectoryPath})]: ${error.message}`); - } - }; - }); - logger.debug('Task finished'); + logger.debug( + `Reading reports directory: ${WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH}`, + ); + fs.readdirSync(WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, { + withFileTypes: true, + }).forEach(fileDirent => { + // If it is a directory and has not a valid MD5 hash, continue the task. + if (fileDirent.isDirectory() && !isMD5(fileDirent.name)) { + // Generate the origin and target path and hash the name + const originDirectoryPath = path.join( + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, + fileDirent.name, + ); + const targetDirectoryName = md5(fileDirent.name); + const targetDirectoryPath = path.join( + WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, + targetDirectoryName, + ); + try { + logger.info( + `Found reports directory to migrate: [${fileDirent.name}]`, + ); + // Rename the directory from origin to target path + fs.renameSync(originDirectoryPath, targetDirectoryPath); + logger.info( + `Renamed directory [${fileDirent.name} (${originDirectoryPath})] to [${targetDirectoryName} (${targetDirectoryPath})]`, + ); + } catch (error) { + logger.error( + `Error renaming directory [${fileDirent.name} (${originDirectoryPath})] to [${targetDirectoryName} (${targetDirectoryPath})]: ${error.message}`, + ); + } + } + }); + logger.debug('Task finished: Migrate reports directory name'); } catch (error) { logger.error(`Error: ${error.message}`); - }; + } } // Check that the text is a valid MD5 hash @@ -65,4 +79,4 @@ export default function migrateReportsDirectoryName(context) { export function isMD5(text: string) { const regexMD5 = /^[a-f0-9]{32}$/gi; return regexMD5.test(text); -} \ No newline at end of file +} diff --git a/plugins/main/server/start/monitoring/index.ts b/plugins/main/server/start/monitoring/index.ts index c17e844897..ca87b5121e 100644 --- a/plugins/main/server/start/monitoring/index.ts +++ b/plugins/main/server/start/monitoring/index.ts @@ -10,25 +10,25 @@ * Find more information about this on the LICENSE file. */ import cron from 'node-cron'; -import { log } from '../../lib/logger'; import { monitoringTemplate } from '../../integration-files/monitoring-template'; import { getConfiguration } from '../../lib/get-configuration'; import { parseCron } from '../../lib/parse-cron'; import { indexDate } from '../../lib/index-date'; import { buildIndexSettings } from '../../lib/build-index-settings'; -import { WazuhHostsCtrl } from '../../controllers/wazuh-hosts'; import { + WAZUH_MONITORING_DEFAULT_CRON_FREQ, WAZUH_MONITORING_TEMPLATE_NAME, } from '../../../common/constants'; import { tryCatchForIndexPermissionError } from '../tryCatchForIndexPermissionError'; import { delayAsPromise } from '../../../common/utils'; import { getSettingDefaultValue } from '../../../common/services/settings'; -const blueWazuh = '\u001b[34mwazuh\u001b[39m'; -const monitoringErrorLogColors = [blueWazuh, 'monitoring', 'error']; -const wazuhHostController = new WazuhHostsCtrl(); - -let MONITORING_ENABLED, MONITORING_FREQUENCY, MONITORING_CRON_FREQ, MONITORING_CREATION, MONITORING_INDEX_PATTERN, MONITORING_INDEX_PREFIX; +let MONITORING_ENABLED, + MONITORING_FREQUENCY, + MONITORING_CRON_FREQ, + MONITORING_CREATION, + MONITORING_INDEX_PATTERN, + MONITORING_INDEX_PREFIX; // Utils functions /** @@ -37,9 +37,15 @@ let MONITORING_ENABLED, MONITORING_FREQUENCY, MONITORING_CRON_FREQ, MONITORING_C * @param configuration * @param defaultValue */ -function getAppConfigurationSetting(setting: string, configuration: any, defaultValue: any) { - return typeof configuration[setting] !== 'undefined' ? configuration[setting] : defaultValue; -}; +function getAppConfigurationSetting( + setting: string, + configuration: any, + defaultValue: any, +) { + return typeof configuration[setting] !== 'undefined' + ? configuration[setting] + : defaultValue; +} /** * Set the monitoring variables @@ -47,48 +53,68 @@ function getAppConfigurationSetting(setting: string, configuration: any, default */ function initMonitoringConfiguration(context) { try { + context.wazuh.logger.debug('Reading configuration'); const appConfig = getConfiguration(); - MONITORING_ENABLED = appConfig && typeof appConfig['wazuh.monitoring.enabled'] !== 'undefined' - ? appConfig['wazuh.monitoring.enabled'] && - appConfig['wazuh.monitoring.enabled'] !== 'worker' - : getSettingDefaultValue('wazuh.monitoring.enabled'); - MONITORING_FREQUENCY = getAppConfigurationSetting('wazuh.monitoring.frequency', appConfig, getSettingDefaultValue('wazuh.monitoring.frequency')); - MONITORING_CRON_FREQ = parseCron(MONITORING_FREQUENCY); - MONITORING_CREATION = getAppConfigurationSetting('wazuh.monitoring.creation', appConfig, getSettingDefaultValue('wazuh.monitoring.creation')); - - MONITORING_INDEX_PATTERN = getAppConfigurationSetting('wazuh.monitoring.pattern', appConfig, getSettingDefaultValue('wazuh.monitoring.pattern')); - const lastCharIndexPattern = MONITORING_INDEX_PATTERN[MONITORING_INDEX_PATTERN.length - 1]; + MONITORING_ENABLED = + appConfig && typeof appConfig['wazuh.monitoring.enabled'] !== 'undefined' + ? appConfig['wazuh.monitoring.enabled'] && + appConfig['wazuh.monitoring.enabled'] !== 'worker' + : getSettingDefaultValue('wazuh.monitoring.enabled'); + MONITORING_FREQUENCY = getAppConfigurationSetting( + 'wazuh.monitoring.frequency', + appConfig, + getSettingDefaultValue('wazuh.monitoring.frequency'), + ); + try { + MONITORING_CRON_FREQ = parseCron(MONITORING_FREQUENCY); + } catch (error) { + context.wazuh.logger.warn( + `Using default value ${WAZUH_MONITORING_DEFAULT_CRON_FREQ} due to: ${ + error.message || error + }`, + ); + MONITORING_CRON_FREQ = WAZUH_MONITORING_DEFAULT_CRON_FREQ; + } + MONITORING_CREATION = getAppConfigurationSetting( + 'wazuh.monitoring.creation', + appConfig, + getSettingDefaultValue('wazuh.monitoring.creation'), + ); + + MONITORING_INDEX_PATTERN = getAppConfigurationSetting( + 'wazuh.monitoring.pattern', + appConfig, + getSettingDefaultValue('wazuh.monitoring.pattern'), + ); + const lastCharIndexPattern = + MONITORING_INDEX_PATTERN[MONITORING_INDEX_PATTERN.length - 1]; if (lastCharIndexPattern !== '*') { MONITORING_INDEX_PATTERN += '*'; - }; - MONITORING_INDEX_PREFIX = MONITORING_INDEX_PATTERN.slice(0, MONITORING_INDEX_PATTERN.length - 1); + } + MONITORING_INDEX_PREFIX = MONITORING_INDEX_PATTERN.slice( + 0, + MONITORING_INDEX_PATTERN.length - 1, + ); - log( - 'monitoring:initMonitoringConfiguration', + context.wazuh.logger.debug( `wazuh.monitoring.enabled: ${MONITORING_ENABLED}`, - 'debug' ); - log( - 'monitoring:initMonitoringConfiguration', + context.wazuh.logger.debug( `wazuh.monitoring.frequency: ${MONITORING_FREQUENCY} (${MONITORING_CRON_FREQ})`, - 'debug' ); - log( - 'monitoring:initMonitoringConfiguration', + context.wazuh.logger.debug( + `wazuh.monitoring.creation: ${MONITORING_CREATION}`, + ); + + context.wazuh.logger.debug( `wazuh.monitoring.pattern: ${MONITORING_INDEX_PATTERN} (index prefix: ${MONITORING_INDEX_PREFIX})`, - 'debug' ); } catch (error) { - const errorMessage = error.message || error; - log( - 'monitoring:initMonitoringConfiguration', - errorMessage - ); - context.wazuh.logger.error(errorMessage) + context.wazuh.logger.error(error.message); } -}; +} /** * Main. First execution when installing / loading App. @@ -98,10 +124,9 @@ async function init(context) { try { if (MONITORING_ENABLED) { await checkTemplate(context); - }; + } } catch (error) { const errorMessage = error.message || error; - log('monitoring:init', error.message || error); context.wazuh.logger.error(errorMessage); } } @@ -111,46 +136,48 @@ async function init(context) { */ async function checkTemplate(context) { try { - log( - 'monitoring:checkTemplate', - 'Updating the monitoring template', - 'debug' - ); - try { + context.wazuh.logger.debug( + `Getting the ${WAZUH_MONITORING_TEMPLATE_NAME} template`, + ); // Check if the template already exists - const currentTemplate = await context.core.opensearch.client.asInternalUser.indices.getTemplate({ - name: WAZUH_MONITORING_TEMPLATE_NAME - }); + const currentTemplate = + await context.core.opensearch.client.asInternalUser.indices.getTemplate( + { + name: WAZUH_MONITORING_TEMPLATE_NAME, + }, + ); // Copy already created index patterns - monitoringTemplate.index_patterns = currentTemplate.body[WAZUH_MONITORING_TEMPLATE_NAME].index_patterns; + monitoringTemplate.index_patterns = + currentTemplate.body[WAZUH_MONITORING_TEMPLATE_NAME].index_patterns; } catch (error) { // Init with the default index pattern - monitoringTemplate.index_patterns = [getSettingDefaultValue('wazuh.monitoring.pattern')]; + monitoringTemplate.index_patterns = [ + getSettingDefaultValue('wazuh.monitoring.pattern'), + ]; } // Check if the user is using a custom pattern and add it to the template if it does if (!monitoringTemplate.index_patterns.includes(MONITORING_INDEX_PATTERN)) { monitoringTemplate.index_patterns.push(MONITORING_INDEX_PATTERN); - }; + } // Update the monitoring template + context.wazuh.logger.debug( + `Updating the ${WAZUH_MONITORING_TEMPLATE_NAME} template`, + ); await context.core.opensearch.client.asInternalUser.indices.putTemplate({ name: WAZUH_MONITORING_TEMPLATE_NAME, - body: monitoringTemplate + body: monitoringTemplate, }); - log( - 'monitoring:checkTemplate', - 'Updated the monitoring template', - 'debug' + context.wazuh.logger.info( + `Updated the ${WAZUH_MONITORING_TEMPLATE_NAME} template`, ); } catch (error) { - const errorMessage = `Something went wrong updating the monitoring template ${error.message || error}`; - log( - 'monitoring:checkTemplate', - errorMessage - ); - context.wazuh.logger.error(monitoringErrorLogColors, errorMessage); + const errorMessage = `Something went wrong updating the ${WAZUH_MONITORING_TEMPLATE_NAME} template ${ + error.message || error + }`; + context.wazuh.logger.error(errorMessage); throw error; } } @@ -161,39 +188,57 @@ async function checkTemplate(context) { * @param {*} data */ async function insertMonitoringDataElasticsearch(context, data) { - const monitoringIndexName = MONITORING_INDEX_PREFIX + indexDate(MONITORING_CREATION); + const monitoringIndexName = + MONITORING_INDEX_PREFIX + indexDate(MONITORING_CREATION); if (!MONITORING_ENABLED) { return; - }; + } try { await tryCatchForIndexPermissionError(monitoringIndexName)(async () => { - const exists = await context.core.opensearch.client.asInternalUser.indices.exists({ index: monitoringIndexName }); + context.wazuh.logger.debug( + `Checking the existence of ${monitoringIndexName} index`, + ); + const exists = + await context.core.opensearch.client.asInternalUser.indices.exists({ + index: monitoringIndexName, + }); if (!exists.body) { + context.wazuh.logger.debug( + `The ${monitoringIndexName} index does not exist`, + ); await createIndex(context, monitoringIndexName); - }; + } else { + context.wazuh.logger.debug(`The ${monitoringIndexName} index exists`); + } // Update the index configuration const appConfig = getConfiguration(); const indexConfiguration = buildIndexSettings( appConfig, 'wazuh.monitoring', - getSettingDefaultValue('wazuh.monitoring.shards') + getSettingDefaultValue('wazuh.monitoring.shards'), ); // To update the index settings with this client is required close the index, update the settings and open it // Number of shards is not dynamic so delete that setting if it's given delete indexConfiguration.settings.index.number_of_shards; + context.wazuh.logger.debug( + `Adding settings to ${monitoringIndexName} index`, + ); await context.core.opensearch.client.asInternalUser.indices.putSettings({ index: monitoringIndexName, - body: indexConfiguration + body: indexConfiguration, }); + context.wazuh.logger.info( + `Settings added to ${monitoringIndexName} index`, + ); + // Insert data to the monitoring index await insertDataToIndex(context, monitoringIndexName, data); })(); } catch (error) { - log('monitoring:insertMonitoringDataElasticsearch', error.message || error); - context.wazuh.logger.error(error.message); + context.wazuh.logger.error(error.message || error); } } @@ -203,39 +248,45 @@ async function insertMonitoringDataElasticsearch(context, data) { * @param {String} indexName The name for the index (e.g. daily: wazuh-monitoring-YYYY.MM.DD) * @param {*} data */ -async function insertDataToIndex(context, indexName: string, data: { agents: any[], apiHost }) { +async function insertDataToIndex( + context, + indexName: string, + data: { agents: any[]; apiHost }, +) { const { agents, apiHost } = data; try { if (agents.length > 0) { - log( - 'monitoring:insertDataToIndex', + context.wazuh.logger.debug( `Bulk data to index ${indexName} for ${agents.length} agents`, - 'debug' ); - const bodyBulk = agents.map(agent => { - const agentInfo = { ...agent }; - agentInfo['timestamp'] = new Date(Date.now()).toISOString(); - agentInfo.host = agent.manager; - agentInfo.cluster = { name: apiHost.clusterName ? apiHost.clusterName : 'disabled' }; - return `{ "index": { "_index": "${indexName}" } }\n${JSON.stringify(agentInfo)}\n`; - }).join(''); + const bodyBulk = agents + .map(agent => { + const agentInfo = { ...agent }; + agentInfo['timestamp'] = new Date(Date.now()).toISOString(); + agentInfo.host = agent.manager; + agentInfo.cluster = { + name: apiHost.clusterName ? apiHost.clusterName : 'disabled', + }; + return `{ "index": { "_index": "${indexName}" } }\n${JSON.stringify( + agentInfo, + )}\n`; + }) + .join(''); await context.core.opensearch.client.asInternalUser.bulk({ index: indexName, - body: bodyBulk + body: bodyBulk, }); - log( - 'monitoring:insertDataToIndex', + context.wazuh.logger.info( `Bulk data to index ${indexName} for ${agents.length} agents completed`, - 'debug' ); } } catch (error) { - log( - 'monitoring:insertDataToIndex', - `Error inserting agent data into elasticsearch. Bulk request failed due to ${error.message || - error}` + context.wazuh.logger.error( + `Error inserting agent data into elasticsearch. Bulk request failed due to ${ + error.message || error + }`, ); } } @@ -253,67 +304,65 @@ async function createIndex(context, indexName: string) { const IndexConfiguration = { settings: { index: { - number_of_shards: getAppConfigurationSetting('wazuh.monitoring.shards', appConfig, getSettingDefaultValue('wazuh.monitoring.shards')), - number_of_replicas: getAppConfigurationSetting('wazuh.monitoring.replicas', appConfig, getSettingDefaultValue('wazuh.monitoring.replicas')) - } - } + number_of_shards: getAppConfigurationSetting( + 'wazuh.monitoring.shards', + appConfig, + getSettingDefaultValue('wazuh.monitoring.shards'), + ), + number_of_replicas: getAppConfigurationSetting( + 'wazuh.monitoring.replicas', + appConfig, + getSettingDefaultValue('wazuh.monitoring.replicas'), + ), + }, + }, }; + context.wazuh.logger.debug(`Creating ${indexName} index`); + await context.core.opensearch.client.asInternalUser.indices.create({ index: indexName, - body: IndexConfiguration + body: IndexConfiguration, }); - log( - 'monitoring:createIndex', - `Successfully created new index: ${indexName}`, - 'debug' - ); + context.wazuh.logger.info(`${indexName} index created`); } catch (error) { - const errorMessage = `Could not create ${indexName} index on elasticsearch due to ${error.message || error}`; - log( - 'monitoring:createIndex', - errorMessage + context.wazuh.logger.error( + `Could not create ${indexName} index: ${error.message || error}`, ); - context.wazuh.logger.error(errorMessage); } } /** -* Wait until Kibana server is ready -*/ + * Wait until Kibana server is ready + */ async function checkPluginPlatformStatus(context) { try { - log( - 'monitoring:checkPluginPlatformStatus', - 'Waiting for Kibana and Elasticsearch servers to be ready...', - 'debug' - ); + context.wazuh.logger.debug('Waiting for platform servers to be ready...'); await checkElasticsearchServer(context); await init(context); - return; } catch (error) { - log( - 'monitoring:checkPluginPlatformStatus', - error.mesage || error - ); + context.wazuh.logger.error(error.message || error); try { await delayAsPromise(3000); await checkPluginPlatformStatus(context); - } catch (error) { }; + } catch (error) {} } } - /** * Check Elasticsearch Server status and Kibana index presence */ async function checkElasticsearchServer(context) { try { - const data = await context.core.opensearch.client.asInternalUser.indices.exists({ - index: context.server.config.opensearchDashboards.index - }); + context.wazuh.logger.debug( + `Checking the existence of ${context.server.config.opensearchDashboards.index} index`, + ); + const data = + await context.core.opensearch.client.asInternalUser.indices.exists({ + index: context.server.config.opensearchDashboards.index, + }); return data.body; // TODO: check if Elasticsearch can receive requests @@ -323,51 +372,47 @@ async function checkElasticsearchServer(context) { // } return Promise.reject(data); } catch (error) { - log('monitoring:checkElasticsearchServer', error.message || error); + context.wazuh.logger.error(error.message || error); return Promise.reject(error); } } -const fakeResponseEndpoint = { - ok: (body: any) => body, - custom: (body: any) => body, -} /** * Get API configuration from elastic and callback to loadCredentials */ -async function getHostsConfiguration() { +async function getHostsConfiguration(context) { try { - const hosts = await wazuhHostController.getHostsEntries(false, false, fakeResponseEndpoint); - if (hosts.body.length) { - return hosts.body; - }; + const hosts = + await context.wazuh_core.serverAPIHostEntries.getHostsEntries(); + if (hosts.length) { + return hosts; + } - log( - 'monitoring:getConfig', - 'There are no Wazuh API entries yet', - 'debug' - ); + context.wazuh.logger.debug('There are no API host entries yet'); return Promise.reject({ error: 'no credentials', - error_code: 1 + error_code: 1, }); } catch (error) { - log('monitoring:getHostsConfiguration', error.message || error); + context.wazuh.logger.error(error.message || error); return Promise.reject({ - error: 'no wazuh hosts', - error_code: 2 + error: 'no API hosts', + error_code: 2, }); } } /** - * Task used by the cron job. - */ + * Task used by the cron job. + */ async function cronTask(context) { try { - const templateMonitoring = await context.core.opensearch.client.asInternalUser.indices.getTemplate({ name: WAZUH_MONITORING_TEMPLATE_NAME }); + const templateMonitoring = + await context.core.opensearch.client.asInternalUser.indices.getTemplate({ + name: WAZUH_MONITORING_TEMPLATE_NAME, + }); - const apiHosts = await getHostsConfiguration(); + const apiHosts = await getHostsConfiguration(context); const apiHostsUnique = (apiHosts || []).filter( (apiHost, index, self) => index === @@ -376,16 +421,17 @@ async function cronTask(context) { t.user === apiHost.user && t.password === apiHost.password && t.url === apiHost.url && - t.port === apiHost.port - ) + t.port === apiHost.port, + ), ); for (let apiHost of apiHostsUnique) { try { const { agents, apiHost: host } = await getApiInfo(context, apiHost); - await insertMonitoringDataElasticsearch(context, { agents, apiHost: host }); - } catch (error) { - - }; + await insertMonitoringDataElasticsearch(context, { + agents, + apiHost: host, + }); + } catch (error) {} } } catch (error) { // Retry to call itself again if Kibana index is not ready yet @@ -399,8 +445,6 @@ async function cronTask(context) { // return cronTask(context); // } // } catch (error) {} //eslint-disable-line - - log('monitoring:cronTask', error.message || error); context.wazuh.logger.error(error.message || error); } } @@ -412,20 +456,34 @@ async function cronTask(context) { */ async function getApiInfo(context, apiHost) { try { - log('monitoring:getApiInfo', `Getting API info for ${apiHost.id}`, 'debug'); - const responseIsCluster = await context.wazuh.api.client.asInternalUser.request('GET', '/cluster/status', {}, { apiHostID: apiHost.id }); - const isCluster = (((responseIsCluster || {}).data || {}).data || {}).enabled === 'yes'; + context.wazuh.logger.debug(`Getting API info for ${apiHost.id}`); + const responseIsCluster = + await context.wazuh.api.client.asInternalUser.request( + 'GET', + '/cluster/status', + {}, + { apiHostID: apiHost.id }, + ); + const isCluster = + (((responseIsCluster || {}).data || {}).data || {}).enabled === 'yes'; if (isCluster) { - const responseClusterInfo = await context.wazuh.api.client.asInternalUser.request('GET', `/cluster/local/info`, {}, { apiHostID: apiHost.id }); - apiHost.clusterName = responseClusterInfo.data.data.affected_items[0].cluster; - }; + const responseClusterInfo = + await context.wazuh.api.client.asInternalUser.request( + 'GET', + `/cluster/local/info`, + {}, + { apiHostID: apiHost.id }, + ); + apiHost.clusterName = + responseClusterInfo.data.data.affected_items[0].cluster; + } const agents = await fetchAllAgentsFromApiHost(context, apiHost); return { agents, apiHost }; } catch (error) { - log('monitoring:getApiInfo', error.message || error); + context.wazuh.logger.error(error.message || error); throw error; } -}; +} /** * Fetch all agents for the API provided @@ -435,25 +493,30 @@ async function getApiInfo(context, apiHost) { async function fetchAllAgentsFromApiHost(context, apiHost) { let agents = []; try { - log('monitoring:fetchAllAgentsFromApiHost', `Getting all agents from ApiID: ${apiHost.id}`, 'debug'); - const responseAgentsCount = await context.wazuh.api.client.asInternalUser.request( - 'GET', - '/agents', - { - params: { - offset: 0, - limit: 1, - q: 'id!=000' - } - }, { apiHostID: apiHost.id }); + context.wazuh.logger.debug(`Getting all agents from ApiID: ${apiHost.id}`); + const responseAgentsCount = + await context.wazuh.api.client.asInternalUser.request( + 'GET', + '/agents', + { + params: { + offset: 0, + limit: 1, + q: 'id!=000', + }, + }, + { apiHostID: apiHost.id }, + ); const agentsCount = responseAgentsCount.data.data.total_affected_items; - log('monitoring:fetchAllAgentsFromApiHost', `ApiID: ${apiHost.id}, Agent count: ${agentsCount}`, 'debug'); + context.wazuh.logger.debug( + `ApiID: ${apiHost.id}, Agent count: ${agentsCount}`, + ); let payload = { offset: 0, limit: 500, - q: 'id!=000' + q: 'id!=000', }; while (agents.length < agentsCount && payload.offset < agentsCount) { @@ -472,29 +535,37 @@ async function fetchAllAgentsFromApiHost(context, apiHost) { - increase the limit of results to retrieve (currently, the requests use the recommended value: 500). See the allowed values. This depends on the selected data because the response could fail if contains a lot of data */ - const responseAgents = await context.wazuh.api.client.asInternalUser.request( - 'GET', - `/agents`, - { params: payload }, - { apiHostID: apiHost.id } - ); + const responseAgents = + await context.wazuh.api.client.asInternalUser.request( + 'GET', + `/agents`, + { params: payload }, + { apiHostID: apiHost.id }, + ); agents = [...agents, ...responseAgents.data.data.affected_items]; payload.offset += payload.limit; } catch (error) { - log('monitoring:fetchAllAgentsFromApiHost', `ApiID: ${apiHost.id}, Error request with offset/limit ${payload.offset}/${payload.limit}: ${error.message || error}`); + context.wazuh.logger.error( + `ApiID: ${apiHost.id}, Error request with offset/limit ${ + payload.offset + }/${payload.limit}: ${error.message || error}`, + ); } } return agents; } catch (error) { - log('monitoring:fetchAllAgentsFromApiHost', `ApiID: ${apiHost.id}. Error: ${error.message || error}`); + context.wazuh.logger.error( + `ApiID: ${apiHost.id}. Error: ${error.message || error}`, + ); throw error; } -}; +} /** * Start the cron job */ export async function jobMonitoringRun(context) { + context.wazuh.logger.debug('Task:Monitoring initializing'); // Init the monitoring variables initMonitoringConfiguration(context); // Check Kibana index and if it is prepared, start the initialization of Wazuh App. @@ -505,4 +576,3 @@ export async function jobMonitoringRun(context) { cron.schedule(MONITORING_CRON_FREQ, () => cronTask(context)); } } - diff --git a/plugins/main/server/start/queue/index.ts b/plugins/main/server/start/queue/index.ts index 36228872c5..707ef2469a 100644 --- a/plugins/main/server/start/queue/index.ts +++ b/plugins/main/server/start/queue/index.ts @@ -10,69 +10,59 @@ * Find more information about this on the LICENSE file. */ import cron from 'node-cron'; -import { log } from '../../lib/logger'; import { WAZUH_QUEUE_CRON_FREQ } from '../../../common/constants'; export let queue = []; -export interface IQueueJob{ +export interface IQueueJob { /** Date object to start the job */ - startAt: Date + startAt: Date; /** Function to execute */ - run: () => void -}; + run: () => void; +} /** * Add a job to the queue. * @param job Job to add to queue */ export function addJobToQueue(job: IQueueJob) { - log('queue:addJob', `New job added`, 'debug'); queue.push(job); -}; +} -async function executePendingJobs() { +async function executePendingJobs(context: any) { try { if (!queue || !queue.length) return; const now: Date = new Date(); const pendingJobs: IQueueJob[] = queue.filter(item => item.startAt <= now); - log( - 'queue:executePendingJobs', - `Pending jobs: ${pendingJobs.length}`, - 'debug' - ); - if (!pendingJobs || !pendingJobs.length){ + context.wazuh.logger.debug(`Pending jobs: ${pendingJobs.length}`); + if (!pendingJobs || !pendingJobs.length) { return; - }; + } queue = queue.filter((item: IQueueJob) => item.startAt > now); for (const job of pendingJobs) { try { - await job.run(); + await job.run(context); } catch (error) { continue; - }; + } } } catch (error) { queue = []; - log('queue:executePendingJobs', error.message || error); return Promise.reject(error); } } /** * Run the job queue it plugin start. - * @param context + * @param context */ export function jobQueueRun(context) { - cron.schedule( - WAZUH_QUEUE_CRON_FREQ, - async () => { - try { - await executePendingJobs(); - } catch (error) { - log('queue:launchCronJob', error.message || error); - } + cron.schedule(WAZUH_QUEUE_CRON_FREQ, async () => { + try { + await executePendingJobs(context); + } catch (error) { + context.wazuh.logger.error(error.message || error); } - ); + }); } diff --git a/plugins/main/server/start/tryCatchForIndexPermissionError.ts b/plugins/main/server/start/tryCatchForIndexPermissionError.ts index 4ddde349a0..c0eee0ee65 100644 --- a/plugins/main/server/start/tryCatchForIndexPermissionError.ts +++ b/plugins/main/server/start/tryCatchForIndexPermissionError.ts @@ -9,27 +9,30 @@ * * Find more information about this on the LICENSE file. */ -import { log } from '../lib/logger'; -export const tryCatchForIndexPermissionError = (wazuhIndex: string) => (functionToTryCatch) => async () => { +export const tryCatchForIndexPermissionError = + (wazuhIndex: string) => functionToTryCatch => async () => { try { - await functionToTryCatch(); + await functionToTryCatch(); + } catch (error) { + enum errorTypes { + SECURITY_EXCEPTION = 'security_exception', + RESPONSE_ERROR = 'Response Error', + } + switch (error.message) { + case errorTypes.SECURITY_EXCEPTION: + error.message = + ( + ( + ((error.meta || error.message).body || error.message).error || + error.message + ).root_cause[0] || error.message + ).reason || error.message; + break; + case errorTypes.RESPONSE_ERROR: + error.message = `Could not check if the index ${wazuhIndex} exists due to no permissions for create, delete or check`; + break; + } + return Promise.reject(error); } - catch (error) { - enum errorTypes{ - SECURITY_EXCEPTION = 'security_exception', - RESPONSE_ERROR = 'Response Error', - } - switch(error.message){ - case errorTypes.SECURITY_EXCEPTION: - error.message = (((((error.meta || error.message).body || error.message).error || error.message).root_cause[0] || error.message).reason || error.message); - break; - case errorTypes.RESPONSE_ERROR: - error.message = `Could not check if the index ${ - wazuhIndex - } exists due to no permissions for create, delete or check`; - break; - } - return Promise.reject(error); - } -} \ No newline at end of file + }; diff --git a/plugins/main/server/types.ts b/plugins/main/server/types.ts index bc80cd26bf..2095695534 100644 --- a/plugins/main/server/types.ts +++ b/plugins/main/server/types.ts @@ -22,5 +22,6 @@ export interface WazuhPluginSetup {} export interface WazuhPluginStart {} export type PluginSetup = { - securityDashboards?: {}, // TODO: Add OpenSearch Dashboards Security interface -} + securityDashboards?: {}; // TODO: Add OpenSearch Dashboards Security interface + wazuhCore: {}; +}; diff --git a/plugins/main/test/cypress/cypress/integration/step-definitions/settings/wazuh-logs/reload-logs.when.js b/plugins/main/test/cypress/cypress/integration/step-definitions/settings/wazuh-logs/reload-logs.when.js deleted file mode 100644 index 19c0f47dc6..0000000000 --- a/plugins/main/test/cypress/cypress/integration/step-definitions/settings/wazuh-logs/reload-logs.when.js +++ /dev/null @@ -1,10 +0,0 @@ -import { clickElement, interceptAs, getSelector } from '../../../utils/driver'; - -import { LOGS_PAGE as pageName} from '../../../utils/pages-constants'; -const reloadLogsLink = getSelector('reloadLogsLink', pageName); - -When('The user reloads the logs', () => { - interceptAs('GET', '/utils/logs', 'apiCheck'); - clickElement(reloadLogsLink); - cy.wait(500); -}); diff --git a/plugins/main/test/server/wazuh-api.js b/plugins/main/test/server/wazuh-api.js index 3c63ae84be..8e48ae980f 100644 --- a/plugins/main/test/server/wazuh-api.js +++ b/plugins/main/test/server/wazuh-api.js @@ -5,7 +5,10 @@ const { PLUGIN_PLATFORM_REQUEST_HEADERS } = require('../../common/constants'); chai.should(); const headers = { - headers: { ...PLUGIN_PLATFORM_REQUEST_HEADERS, 'content-type': 'application/json' } + headers: { + ...PLUGIN_PLATFORM_REQUEST_HEADERS, + 'content-type': 'application/json', + }, }; let API_ID = null; @@ -35,7 +38,7 @@ describe('wazuh-api', () => { 'post', `localhost:5601/api/csv`, { path: '/agents', id: API_ID }, - headers + headers, ); res.body.should.be.a('string'); }); @@ -45,7 +48,7 @@ describe('wazuh-api', () => { 'post', `localhost:5601/api/check-api`, { username: API_USERNAME, url: API_URL, port: API_PORT, id: API_ID }, - headers + headers, ); res.body.should.be.a('object'); res.body.manager.should.be.a('string'); @@ -58,7 +61,7 @@ describe('wazuh-api', () => { 'post', `localhost:5601/api/check-stored-api`, API_ID, - headers + headers, ); res.body.should.be.a('object'); res.body.statusCode.should.be.eql(200); @@ -76,7 +79,7 @@ describe('wazuh-api', () => { 'post', `localhost:5601/api/request`, { method: 'GET', path: '/agents/000', body: {}, id: API_ID }, - headers + headers, ); res.body.should.be.a('object'); res.body.error.should.be.eql(0); @@ -89,7 +92,7 @@ describe('wazuh-api', () => { const res = await needle('get', `localhost:5601/api/pci/all`, {}, {}); res.body.should.be.a('object'); res.body['1.1.1'].should.be.eql( - 'A formal process for approving and testing all network connections and changes to the firewall and router configurations' + 'A formal process for approving and testing all network connections and changes to the firewall and router configurations', ); }); @@ -97,7 +100,7 @@ describe('wazuh-api', () => { const res = await needle('get', `localhost:5601/api/gdpr/all`, {}, {}); res.body.should.be.a('object'); res.body['II_5.1.f'].should.be.eql( - 'Ensure the ongoing confidentiality, integrity, availability and resilience of processing systems and services, verifying its modifications, accesses, locations and guarantee the safety of them.
File sharing protection and file sharing technologies that meet the requirements of data protection.' + 'Ensure the ongoing confidentiality, integrity, availability and resilience of processing systems and services, verifying its modifications, accesses, locations and guarantee the safety of them.
File sharing protection and file sharing technologies that meet the requirements of data protection.', ); }); @@ -106,7 +109,7 @@ describe('wazuh-api', () => { 'get', `localhost:5601/utils/configuration`, {}, - {} + {}, ); res.body.should.be.a('object'); res.body.error.should.be.eql(0); @@ -120,11 +123,4 @@ describe('wazuh-api', () => { res.body.error.should.be.eql(0); res.body.ram.should.be.gt(1); }); - - it('GET /utils/logs', async () => { - const res = await needle('get', `localhost:5601/utils/logs`, {}, {}); - res.body.should.be.a('object'); - res.body.lastLogs.should.be.a('array'); - res.body.error.should.be.eql(0); - }); }); diff --git a/plugins/main/yarn.lock b/plugins/main/yarn.lock index b04bca1416..527f2d2879 100644 --- a/plugins/main/yarn.lock +++ b/plugins/main/yarn.lock @@ -15,20 +15,6 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.13.11" -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -509,11 +495,6 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== -"@types/triple-beam@^1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" - integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== - "@types/tz-offset@*": version "0.0.0" resolved "https://registry.yarnpkg.com/@types/tz-offset/-/tz-offset-0.0.0.tgz#d58f1cebd794148d245420f8f0660305d320e565" @@ -803,11 +784,6 @@ ast-types@^0.7.0: resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.7.8.tgz#902d2e0d60d071bdcd46dc115e1809ed11c138a9" integrity sha512-RIOpVnVlltB6PcBJ5BMLx+H+6JJ/zjDGU0t7f0L6c2M1dqcK92VQopLBlPQ9R80AVXelfqYgjcPLtHtDbNFg0Q== -async@^3.2.3: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -989,13 +965,6 @@ codemirror@^5.18.2: resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.14.tgz#e75fbc7247453f1baa71463c33b52adba7e41b2a" integrity sha512-VSNugIBDGt0OU9gDjeVr6fNkoFQznrWEUdAApMlXQNbfE8gGO19776D6MwSqF/V/w/sDwonsQ0z7KmmI9guScg== -color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -1003,40 +972,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1263,11 +1203,6 @@ duplexer2@~0.1.4: dependencies: readable-stream "^2.0.2" -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -1754,11 +1689,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -1794,11 +1724,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" @@ -2125,11 +2050,6 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: get-intrinsic "^1.2.0" is-typed-array "^1.1.10" -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -2365,11 +2285,6 @@ jwt-decode@^3.1.2: resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -2423,18 +2338,6 @@ lodash@^4.15.0, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -logform@^2.3.2, logform@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.5.1.tgz#44c77c34becd71b3a42a3970c77929e52c6ed48b" - integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg== - dependencies: - "@colors/colors" "1.5.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - loglevel@^1.7.1: version "1.8.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" @@ -2739,13 +2642,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -3054,7 +2950,7 @@ readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@~2.3.3, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -3181,11 +3077,6 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -safe-stable-stringify@^2.3.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -3271,13 +3162,6 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3310,11 +3194,6 @@ sourcemap-codec@^1.4.1: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - stampit@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/stampit/-/stampit-4.3.2.tgz#cfd3f607dd628a161ce6305621597994b4d56573" @@ -3503,11 +3382,6 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -3594,11 +3468,6 @@ tree-sitter@=0.20.4: nan "^2.17.0" prebuild-install "^7.1.1" -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - ts-api-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" @@ -3832,32 +3701,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== - dependencies: - logform "^2.3.2" - readable-stream "^3.6.0" - triple-beam "^1.3.0" - -winston@3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.9.0.tgz#2bbdeb8167a75fac6d9a0c6d002890cd908016c2" - integrity sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ== - dependencies: - "@colors/colors" "1.5.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.4.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.5.0" - word-wrap@~1.2.3: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" diff --git a/plugins/wazuh-check-updates/package.json b/plugins/wazuh-check-updates/package.json index 1f3a34506a..4804025d53 100644 --- a/plugins/wazuh-check-updates/package.json +++ b/plugins/wazuh-check-updates/package.json @@ -20,8 +20,7 @@ "dependencies": { "axios": "^1.6.1", "md5": "^2.3.0", - "node-cron": "^3.0.2", - "winston": "^3.10.0" + "node-cron": "^3.0.2" }, "devDependencies": { "@testing-library/user-event": "^14.5.0", diff --git a/plugins/wazuh-check-updates/server/plugin-services.ts b/plugins/wazuh-check-updates/server/plugin-services.ts index 1d5e14b699..016c19a7a6 100644 --- a/plugins/wazuh-check-updates/server/plugin-services.ts +++ b/plugins/wazuh-check-updates/server/plugin-services.ts @@ -1,10 +1,14 @@ -import { CoreStart, ISavedObjectsRepository } from 'opensearch-dashboards/server'; +import { + CoreStart, + ISavedObjectsRepository, +} from 'opensearch-dashboards/server'; import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/common'; import { WazuhCorePluginStart } from '../../wazuh-core/server'; -export const [getInternalSavedObjectsClient, setInternalSavedObjectsClient] = createGetterSetter< - ISavedObjectsRepository ->('SavedObjectsRepository'); +export const [getInternalSavedObjectsClient, setInternalSavedObjectsClient] = + createGetterSetter('SavedObjectsRepository'); export const [getCore, setCore] = createGetterSetter('Core'); export const [getWazuhCore, setWazuhCore] = - createGetterSetter('WazuhCore'); \ No newline at end of file + createGetterSetter('WazuhCore'); +export const [getWazuhCheckUpdatesServices, setWazuhCheckUpdatesServices] = + createGetterSetter('WazuhCheckUpdatesServices'); diff --git a/plugins/wazuh-check-updates/server/plugin.ts b/plugins/wazuh-check-updates/server/plugin.ts index 105e29eee7..4b69b26171 100644 --- a/plugins/wazuh-check-updates/server/plugin.ts +++ b/plugins/wazuh-check-updates/server/plugin.ts @@ -13,8 +13,16 @@ import { AppPluginStartDependencies, } from './types'; import { defineRoutes } from './routes'; -import { availableUpdatesObject, userPreferencesObject } from './services/saved-object/types'; -import { setCore, setWazuhCore, setInternalSavedObjectsClient } from './plugin-services'; +import { + availableUpdatesObject, + userPreferencesObject, +} from './services/saved-object/types'; +import { + setCore, + setWazuhCore, + setInternalSavedObjectsClient, + setWazuhCheckUpdatesServices, +} from './plugin-services'; import { ISecurityFactory } from '../../wazuh-core/server/services/security-factory'; declare module 'opensearch-dashboards/server' { @@ -27,7 +35,8 @@ declare module 'opensearch-dashboards/server' { } export class WazuhCheckUpdatesPlugin - implements Plugin { + implements Plugin +{ private readonly logger: Logger; constructor(initializerContext: PluginInitializerContext) { @@ -37,10 +46,13 @@ export class WazuhCheckUpdatesPlugin public async setup(core: CoreSetup, plugins: PluginSetup) { this.logger.debug('wazuh_check_updates: Setup'); + setWazuhCore(plugins.wazuhCore); + setWazuhCheckUpdatesServices({ logger: this.logger }); + core.http.registerRouteHandlerContext('wazuh_check_updates', () => { return { logger: this.logger, - security: plugins.wazuhCore.wazuhSecurity, + security: plugins.wazuhCore.dashboardSecurity, }; }); @@ -56,12 +68,16 @@ export class WazuhCheckUpdatesPlugin return {}; } - public start(core: CoreStart, plugins: AppPluginStartDependencies): WazuhCheckUpdatesPluginStart { + public start( + core: CoreStart, + plugins: AppPluginStartDependencies, + ): WazuhCheckUpdatesPluginStart { this.logger.debug('wazuhCheckUpdates: Started'); - const internalSavedObjectsClient = core.savedObjects.createInternalRepository(); + const internalSavedObjectsClient = + core.savedObjects.createInternalRepository(); setCore(core); - setWazuhCore(plugins.wazuhCore); + setInternalSavedObjectsClient(internalSavedObjectsClient); return {}; diff --git a/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.test.ts b/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.test.ts index bc12157705..58d771e7f8 100644 --- a/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.test.ts +++ b/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.test.ts @@ -1,8 +1,14 @@ -import { getInternalSavedObjectsClient, getWazuhCore } from '../../plugin-services'; +import { + getInternalSavedObjectsClient, + getWazuhCore, + getWazuhCheckUpdatesServices, +} from '../../plugin-services'; import { getSavedObject } from './get-saved-object'; -const mockedGetInternalObjectsClient = getInternalSavedObjectsClient as jest.Mock; -const mockedGetWazuhCore = getWazuhCore as jest.Mock; +const mockedGetInternalObjectsClient = + getInternalSavedObjectsClient as jest.Mock; +const mockedGetWazuhCheckUpdatesServices = + getWazuhCheckUpdatesServices as jest.Mock; jest.mock('../../plugin-services'); describe('getSavedObject function', () => { @@ -24,8 +30,13 @@ describe('getSavedObject function', () => { mockedGetInternalObjectsClient.mockImplementation(() => ({ get: jest.fn().mockRejectedValue({ output: { statusCode: 404 } }), })); - mockedGetWazuhCore.mockImplementation(() => ({ - services: { log: jest.fn().mockImplementation(() => {}) }, + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, })); const response = await getSavedObject('type'); @@ -37,8 +48,13 @@ describe('getSavedObject function', () => { mockedGetInternalObjectsClient.mockImplementation(() => ({ get: jest.fn().mockRejectedValue(new Error('getSavedObject error')), })); - mockedGetWazuhCore.mockImplementation(() => ({ - services: { log: jest.fn().mockImplementation(() => {}) }, + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, })); const promise = getSavedObject('type'); diff --git a/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.ts b/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.ts index e7184cfd3b..fec5c3a548 100644 --- a/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.ts +++ b/plugins/wazuh-check-updates/server/services/saved-object/get-saved-object.ts @@ -1,7 +1,13 @@ -import { getInternalSavedObjectsClient, getWazuhCore } from '../../plugin-services'; +import { + getInternalSavedObjectsClient, + getWazuhCheckUpdatesServices, +} from '../../plugin-services'; import { savedObjectType } from '../../../common/types'; -export const getSavedObject = async (type: string, id?: string): Promise => { +export const getSavedObject = async ( + type: string, + id?: string, +): Promise => { try { const client = getInternalSavedObjectsClient(); @@ -20,11 +26,9 @@ export const getSavedObject = async (type: string, id?: string): Promise { @@ -14,11 +20,19 @@ describe('setSavedObject function', () => { mockedGetInternalObjectsClient.mockImplementation(() => ({ create: () => ({ attributes: { hide_update_notifications: true } }), })); + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, + })); const response = await setSavedObject( 'wazuh-check-updates-user-preferences', { hide_update_notifications: true }, - 'admin' + 'admin', ); expect(response).toEqual({ hide_update_notifications: true }); @@ -28,14 +42,19 @@ describe('setSavedObject function', () => { mockedGetInternalObjectsClient.mockImplementation(() => ({ create: jest.fn().mockRejectedValue(new Error('setSavedObject error')), })); - mockedGetWazuhCore.mockImplementation(() => ({ - services: { log: jest.fn().mockImplementation(() => {}) }, + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, })); const promise = setSavedObject( 'wazuh-check-updates-user-preferences', { hide_update_notifications: true }, - 'admin' + 'admin', ); await expect(promise).rejects.toThrow('setSavedObject error'); diff --git a/plugins/wazuh-check-updates/server/services/saved-object/set-saved-object.ts b/plugins/wazuh-check-updates/server/services/saved-object/set-saved-object.ts index 64820cd54e..5e45bda413 100644 --- a/plugins/wazuh-check-updates/server/services/saved-object/set-saved-object.ts +++ b/plugins/wazuh-check-updates/server/services/saved-object/set-saved-object.ts @@ -1,10 +1,13 @@ import { savedObjectType } from '../../../common/types'; -import { getInternalSavedObjectsClient, getWazuhCore } from '../../plugin-services'; +import { + getInternalSavedObjectsClient, + getWazuhCheckUpdatesServices, +} from '../../plugin-services'; export const setSavedObject = async ( type: string, value: savedObjectType, - id?: string + id?: string, ): Promise => { try { const client = getInternalSavedObjectsClient(); @@ -24,11 +27,9 @@ export const setSavedObject = async ( ? error : 'Error trying to update saved object'; - const { - services: { log }, - } = getWazuhCore(); + const { logger } = getWazuhCheckUpdatesServices(); - log('wazuh-check-updates:setSavedObject', message); + logger.error(message); return Promise.reject(error); } }; diff --git a/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts b/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts index 390e10436b..cc51533f2a 100644 --- a/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts +++ b/plugins/wazuh-check-updates/server/services/updates/get-updates.test.ts @@ -1,6 +1,9 @@ import { getSavedObject } from '../saved-object/get-saved-object'; import { setSavedObject } from '../saved-object/set-saved-object'; -import { getWazuhCore } from '../../plugin-services'; +import { + getWazuhCheckUpdatesServices, + getWazuhCore, +} from '../../plugin-services'; import { API_UPDATES_STATUS } from '../../../common/types'; import { getUpdates } from './get-updates'; import { SAVED_OBJECT_UPDATES } from '../../../common/constants'; @@ -12,6 +15,8 @@ const mockedSetSavedObject = setSavedObject as jest.Mock; jest.mock('../saved-object/set-saved-object'); const mockedGetWazuhCore = getWazuhCore as jest.Mock; +const mockedGetWazuhCheckUpdatesServices = + getWazuhCheckUpdatesServices as jest.Mock; jest.mock('../../plugin-services'); describe('getUpdates function', () => { @@ -43,6 +48,21 @@ describe('getUpdates function', () => { ], })); + mockedGetWazuhCore.mockImplementation(() => ({ + serverAPIHostEntries: { + getHostsEntries: jest.fn(() => []), + }, + })); + + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, + })); + const updates = await getUpdates(); expect(getSavedObject).toHaveBeenCalledTimes(1); @@ -73,6 +93,7 @@ describe('getUpdates function', () => { }); test('should return available updates from api', async () => { + mockedSetSavedObject.mockImplementation(() => ({})); mockedGetWazuhCore.mockImplementation(() => ({ controllers: { WazuhHostsCtrl: jest.fn().mockImplementation(() => ({ @@ -81,36 +102,44 @@ describe('getUpdates function', () => { .mockImplementation(() => [{ id: 'api id' }]), })), }, - services: { - wazuhApiClient: { - client: { - asInternalUser: { - request: jest.fn().mockImplementation(() => ({ + api: { + client: { + asInternalUser: { + request: jest.fn().mockImplementation(() => ({ + data: { data: { - data: { - uuid: '7f828fd6-ef68-4656-b363-247b5861b84c', - current_version: '4.3.1', - last_available_patch: { - description: - '## Manager\r\n\r\n### Fixed\r\n\r\n- Fixed a crash when overwrite rules are triggered...', - published_date: '2022-05-18T10:12:43Z', - semver: { - major: 4, - minor: 3, - patch: 8, - }, - tag: 'v4.3.8', - title: 'Wazuh v4.3.8', + uuid: '7f828fd6-ef68-4656-b363-247b5861b84c', + current_version: '4.3.1', + last_available_patch: { + description: + '## Manager\r\n\r\n### Fixed\r\n\r\n- Fixed a crash when overwrite rules are triggered...', + published_date: '2022-05-18T10:12:43Z', + semver: { + major: 4, + minor: 3, + patch: 8, }, + tag: 'v4.3.8', + title: 'Wazuh v4.3.8', }, }, - })), - }, + }, + })), }, }, }, + serverAPIHostEntries: { + getHostsEntries: jest.fn(() => [{ id: 'api id' }]), + }, + })); + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, })); - mockedSetSavedObject.mockImplementation(() => ({})); const updates = await getUpdates(true); diff --git a/plugins/wazuh-check-updates/server/services/updates/get-updates.ts b/plugins/wazuh-check-updates/server/services/updates/get-updates.ts index 7f1cf39312..59fc819c5d 100644 --- a/plugins/wazuh-check-updates/server/services/updates/get-updates.ts +++ b/plugins/wazuh-check-updates/server/services/updates/get-updates.ts @@ -5,26 +5,30 @@ import { } from '../../../common/types'; import { SAVED_OBJECT_UPDATES } from '../../../common/constants'; import { getSavedObject, setSavedObject } from '../saved-object'; -import { getWazuhCore } from '../../plugin-services'; +import { + getWazuhCheckUpdatesServices, + getWazuhCore, +} from '../../plugin-services'; -export const getUpdates = async (checkAvailableUpdates?: boolean): Promise => { +export const getUpdates = async ( + checkAvailableUpdates?: boolean, +): Promise => { try { if (!checkAvailableUpdates) { - const availableUpdates = (await getSavedObject(SAVED_OBJECT_UPDATES)) as AvailableUpdates; + const availableUpdates = (await getSavedObject( + SAVED_OBJECT_UPDATES, + )) as AvailableUpdates; return availableUpdates; } - const { - controllers: { WazuhHostsCtrl }, - services: { wazuhApiClient }, - } = getWazuhCore(); - const wazuhHostsController = new WazuhHostsCtrl(); + const { serverAPIHostEntries, api: wazuhApiClient } = getWazuhCore(); - const hosts: { id: string }[] = await wazuhHostsController.getHostsEntries(); + const hosts: { id: string }[] = + await serverAPIHostEntries.getHostsEntries(); const apisAvailableUpdates = await Promise.all( - hosts?.map(async (api) => { + hosts?.map(async api => { const data = {}; const method = 'GET'; const path = '/manager/version/check?force_query=true'; @@ -37,7 +41,7 @@ export const getUpdates = async (checkAvailableUpdates?: boolean): Promise { @@ -25,10 +30,22 @@ describe('getUserPreferences function', () => { hide_update_notifications: false, })); + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, + })); + const response = await getUserPreferences('admin'); expect(getSavedObject).toHaveBeenCalledTimes(1); - expect(getSavedObject).toHaveBeenCalledWith(SAVED_OBJECT_USER_PREFERENCES, 'admin'); + expect(getSavedObject).toHaveBeenCalledWith( + SAVED_OBJECT_USER_PREFERENCES, + 'admin', + ); expect(response).toEqual({ last_dismissed_updates: [ @@ -44,10 +61,6 @@ describe('getUserPreferences function', () => { test('should return an error', async () => { mockedGetSavedObject.mockRejectedValue(new Error('getSavedObject error')); - mockedGetWazuhCore.mockImplementation(() => ({ - services: { log: jest.fn().mockImplementation(() => {}) }, - })); - const promise = getUserPreferences('admin'); expect(getSavedObject).toHaveBeenCalledTimes(1); diff --git a/plugins/wazuh-check-updates/server/services/user-preferences/get-user-preferences.ts b/plugins/wazuh-check-updates/server/services/user-preferences/get-user-preferences.ts index c5b7980985..07562b675c 100644 --- a/plugins/wazuh-check-updates/server/services/user-preferences/get-user-preferences.ts +++ b/plugins/wazuh-check-updates/server/services/user-preferences/get-user-preferences.ts @@ -2,13 +2,15 @@ import _ from 'lodash'; import { SAVED_OBJECT_USER_PREFERENCES } from '../../../common/constants'; import { UserPreferences } from '../../../common/types'; import { getSavedObject } from '../saved-object'; -import { getWazuhCore } from '../../plugin-services'; +import { getWazuhCheckUpdatesServices } from '../../plugin-services'; -export const getUserPreferences = async (username: string): Promise => { +export const getUserPreferences = async ( + username: string, +): Promise => { try { const userPreferences = (await getSavedObject( SAVED_OBJECT_USER_PREFERENCES, - username + username, )) as UserPreferences; const userPreferencesWithoutUsername = _.omit(userPreferences, 'username'); @@ -22,11 +24,9 @@ export const getUserPreferences = async (username: string): Promise { @@ -30,6 +35,14 @@ describe('updateUserPreferences function', () => { })); mockedSetSavedObject.mockImplementation(() => {}); + mockedGetWazuhCheckUpdatesServices.mockImplementation(() => ({ + logger: { + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, + })); const response = await updateUserPreferences('admin', { last_dismissed_updates: [ @@ -42,7 +55,10 @@ describe('updateUserPreferences function', () => { }); expect(getSavedObject).toHaveBeenCalledTimes(1); - expect(getSavedObject).toHaveBeenCalledWith(SAVED_OBJECT_USER_PREFERENCES, 'admin'); + expect(getSavedObject).toHaveBeenCalledWith( + SAVED_OBJECT_USER_PREFERENCES, + 'admin', + ); expect(response).toEqual({ last_dismissed_updates: [ @@ -58,10 +74,6 @@ describe('updateUserPreferences function', () => { test('should return an error', async () => { mockedSetSavedObject.mockRejectedValue(new Error('getSavedObject error')); - mockedGetWazuhCore.mockImplementation(() => ({ - services: { log: jest.fn().mockImplementation(() => {}) }, - })); - const promise = updateUserPreferences('admin', { last_dismissed_updates: [ { diff --git a/plugins/wazuh-check-updates/server/services/user-preferences/update-user-preferences.ts b/plugins/wazuh-check-updates/server/services/user-preferences/update-user-preferences.ts index 3c004eccf1..a4c50b9992 100644 --- a/plugins/wazuh-check-updates/server/services/user-preferences/update-user-preferences.ts +++ b/plugins/wazuh-check-updates/server/services/user-preferences/update-user-preferences.ts @@ -1,19 +1,26 @@ import { SAVED_OBJECT_USER_PREFERENCES } from '../../../common/constants'; import { UserPreferences } from '../../../common/types'; -import { getWazuhCore } from '../../plugin-services'; +import { getWazuhCheckUpdatesServices } from '../../plugin-services'; import { getSavedObject, setSavedObject } from '../saved-object'; export const updateUserPreferences = async ( username: string, - preferences: UserPreferences + preferences: UserPreferences, ): Promise => { try { const userPreferences = - ((await getSavedObject(SAVED_OBJECT_USER_PREFERENCES, username)) as UserPreferences) || {}; + ((await getSavedObject( + SAVED_OBJECT_USER_PREFERENCES, + username, + )) as UserPreferences) || {}; const newUserPreferences = { ...userPreferences, ...preferences }; - await setSavedObject(SAVED_OBJECT_USER_PREFERENCES, newUserPreferences, username); + await setSavedObject( + SAVED_OBJECT_USER_PREFERENCES, + newUserPreferences, + username, + ); return newUserPreferences; } catch (error) { @@ -24,11 +31,9 @@ export const updateUserPreferences = async ( ? error : 'Error trying to update user preferences'; - const { - services: { log }, - } = getWazuhCore(); + const { logger } = getWazuhCheckUpdatesServices(); - log('wazuh-check-updates:getUserPreferences', message); + logger.error(message); return Promise.reject(error); } }; diff --git a/plugins/wazuh-check-updates/server/types.ts b/plugins/wazuh-check-updates/server/types.ts index 477d209c12..6a59f36b2b 100644 --- a/plugins/wazuh-check-updates/server/types.ts +++ b/plugins/wazuh-check-updates/server/types.ts @@ -10,7 +10,7 @@ export interface WazuhCheckUpdatesPluginStart {} export type PluginSetup = { securityDashboards?: {}; // TODO: Add OpenSearch Dashboards Security interface - wazuhCore: { wazuhSecurity: ISecurityFactory }; + wazuhCore: { dashboardSecurity: ISecurityFactory }; }; export interface AppPluginStartDependencies { diff --git a/plugins/wazuh-check-updates/yarn.lock b/plugins/wazuh-check-updates/yarn.lock index 79110b0f40..12a43703da 100644 --- a/plugins/wazuh-check-updates/yarn.lock +++ b/plugins/wazuh-check-updates/yarn.lock @@ -2,20 +2,6 @@ # yarn lockfile v1 -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - "@testing-library/user-event@^14.5.0": version "14.5.0" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.0.tgz#4036add379525b635a64bce4d727820d4ba516a7" @@ -35,16 +21,6 @@ resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.8.tgz#c4d774b86bf8250d1e9046e08b17875c21ae64eb" integrity sha512-+z5VrCvLwiJUohbRSgHdyZnHzAaLuD/E2bBANw+NQ1l05Crj8dIxb/kKK+OEqRitV2Wr/LYLuEBenGDsHZVV5Q== -"@types/triple-beam@^1.3.2": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.3.tgz#726ae98a5f6418c8f24f9b0f2a9f81a8664876ae" - integrity sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g== - -async@^3.2.3: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -64,47 +40,6 @@ charenc@0.0.2: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== -color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -122,21 +57,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - follow-redirects@^1.15.0: version "1.15.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" @@ -151,43 +71,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -logform@^2.3.2, logform@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.5.1.tgz#44c77c34becd71b3a42a3970c77929e52c6ed48b" - integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg== - dependencies: - "@colors/colors" "1.5.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -209,11 +97,6 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - node-cron@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" @@ -221,98 +104,12 @@ node-cron@^3.0.2: dependencies: uuid "8.3.2" -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-stable-stringify@^2.3.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - uuid@8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== - dependencies: - logform "^2.3.2" - readable-stream "^3.6.0" - triple-beam "^1.3.0" - -winston@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.10.0.tgz#d033cb7bd3ced026fed13bf9d92c55b903116803" - integrity sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g== - dependencies: - "@colors/colors" "1.5.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.4.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.5.0" diff --git a/plugins/wazuh-core/common/api-user-status-run-as.ts b/plugins/wazuh-core/common/api-user-status-run-as.ts new file mode 100644 index 0000000000..1aae9eef7e --- /dev/null +++ b/plugins/wazuh-core/common/api-user-status-run-as.ts @@ -0,0 +1,23 @@ +/** + * @example + * HOST = set in wazuh.yml config + * USER = set in user interface + * + * ALL_DISABLED + * binary 00 = decimal 0 ---> USER 0 y HOST 0 + * + * USER_NOT_ALLOWED + * binary 01 = decimal 1 ---> USER 0 y HOST 1 + * + * HOST_DISABLED + * binary 10 = decimal 2 ---> USER 1 y HOST 0 + * + * ENABLED + * binary 11 = decimal 3 ---> USER 1 y HOST 1 + */ +export enum API_USER_STATUS_RUN_AS { + ALL_DISABLED = 0, // Wazuh HOST and USER API user configured with run_as=false or undefined + USER_NOT_ALLOWED = 1, // Wazuh HOST API user configured with run_as = TRUE in wazuh.yml but it has not run_as in Wazuh API + HOST_DISABLED = 2, // Wazuh HOST API user configured with run_as=false in wazuh.yml but it has not run_as in Wazuh API + ENABLED = 3, // Wazuh API user configured with run_as=true and allow run_as +} diff --git a/plugins/wazuh-core/common/constants.ts b/plugins/wazuh-core/common/constants.ts index 08b4ac45c2..231a5a3a56 100644 --- a/plugins/wazuh-core/common/constants.ts +++ b/plugins/wazuh-core/common/constants.ts @@ -137,35 +137,6 @@ export const WAZUH_DATA_CONFIG_REGISTRY_PATH = path.join( 'wazuh-registry.json', ); -// Wazuh data path - logs -export const MAX_MB_LOG_FILES = 100; -export const WAZUH_DATA_LOGS_DIRECTORY_PATH = path.join( - WAZUH_DATA_ABSOLUTE_PATH, - 'logs', -); -export const WAZUH_DATA_LOGS_PLAIN_FILENAME = 'wazuhapp-plain.log'; -export const WAZUH_DATA_LOGS_PLAIN_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_DATA_LOGS_PLAIN_FILENAME, -); -export const WAZUH_DATA_LOGS_RAW_FILENAME = 'wazuhapp.log'; -export const WAZUH_DATA_LOGS_RAW_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_DATA_LOGS_RAW_FILENAME, -); - -// Wazuh data path - UI logs -export const WAZUH_UI_LOGS_PLAIN_FILENAME = 'wazuh-ui-plain.log'; -export const WAZUH_UI_LOGS_RAW_FILENAME = 'wazuh-ui.log'; -export const WAZUH_UI_LOGS_PLAIN_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_UI_LOGS_PLAIN_FILENAME, -); -export const WAZUH_UI_LOGS_RAW_PATH = path.join( - WAZUH_DATA_LOGS_DIRECTORY_PATH, - WAZUH_UI_LOGS_RAW_FILENAME, -); - // Wazuh data path - downloads export const WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH = path.join( WAZUH_DATA_ABSOLUTE_PATH, @@ -1503,38 +1474,6 @@ export const PLUGIN_SETTINGS: { [key: string]: TPluginSetting } = { return schema.boolean(); }, }, - 'logs.level': { - title: 'Log level', - description: 'Logging level of the App.', - category: SettingCategory.GENERAL, - type: EpluginSettingType.select, - options: { - select: [ - { - text: 'Info', - value: 'info', - }, - { - text: 'Debug', - value: 'debug', - }, - ], - }, - defaultValue: 'info', - isConfigurableFromFile: true, - isConfigurableFromUI: true, - requiresRestartingPluginPlatform: 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)), - ); - }, - }, pattern: { title: 'Index pattern', description: diff --git a/plugins/wazuh-core/docs/README.md b/plugins/wazuh-core/docs/README.md new file mode 100644 index 0000000000..7e51a684fd --- /dev/null +++ b/plugins/wazuh-core/docs/README.md @@ -0,0 +1,20 @@ +# Description + +The plugin creates and provides instances of the core functionalities services to be shared with other plugins. They are exposed +through the plugin lifecycle methods. + +# Backend + +This plugin provides some core services: + +- CacheAPIUserAllowRunAs: caches the status of API host internal user allows the run as option +- ManageHosts: manage the API host entries +- ServerAPIClient: communicates with the Wazuh server APIs +- ServerAPIHostEntries: gets information about the API host entries +- UpdateConfigurationFile: updates the configuration file +- UpdateRegistry: updates the registry file + +## Frontend + +- Utils +- Constants diff --git a/plugins/wazuh-core/package.json b/plugins/wazuh-core/package.json index 82522f6cba..1317a3b47c 100644 --- a/plugins/wazuh-core/package.json +++ b/plugins/wazuh-core/package.json @@ -22,8 +22,7 @@ "json2csv": "^4.1.2", "jwt-decode": "^3.1.2", "md5": "^2.3.0", - "node-cron": "^3.0.2", - "winston": "^3.10.0" + "node-cron": "^3.0.2" }, "devDependencies": { "@testing-library/user-event": "^14.5.0", diff --git a/plugins/wazuh-core/public/plugin.ts b/plugins/wazuh-core/public/plugin.ts index 8b3078adba..e6d9498318 100644 --- a/plugins/wazuh-core/public/plugin.ts +++ b/plugins/wazuh-core/public/plugin.ts @@ -2,12 +2,16 @@ import { CoreSetup, CoreStart, Plugin } from 'opensearch-dashboards/public'; import { WazuhCorePluginSetup, WazuhCorePluginStart } from './types'; import { setCore, setUiSettings } from './plugin-services'; import * as utils from './utils'; +import { API_USER_STATUS_RUN_AS } from '../common/api-user-status-run-as'; export class WazuhCorePlugin implements Plugin { public setup(core: CoreSetup): WazuhCorePluginSetup { - return {}; + return { + utils, + API_USER_STATUS_RUN_AS, + }; } public start(core: CoreStart): WazuhCorePluginStart { @@ -16,6 +20,7 @@ export class WazuhCorePlugin return { utils, + API_USER_STATUS_RUN_AS, }; } diff --git a/plugins/wazuh-core/public/types.ts b/plugins/wazuh-core/public/types.ts index 28803ab6df..62cb106877 100644 --- a/plugins/wazuh-core/public/types.ts +++ b/plugins/wazuh-core/public/types.ts @@ -1,7 +1,13 @@ -export interface WazuhCorePluginSetup {} +import { API_USER_STATUS_RUN_AS } from '../common/api-user-status-run-as'; + +export interface WazuhCorePluginSetup { + utils: { formatUIDate: (date: Date) => string }; + API_USER_STATUS_RUN_AS: API_USER_STATUS_RUN_AS; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface WazuhCorePluginStart { utils: { formatUIDate: (date: Date) => string }; + API_USER_STATUS_RUN_AS: API_USER_STATUS_RUN_AS; } export interface AppPluginStartDependencies {} diff --git a/plugins/wazuh-core/server/controllers/index.ts b/plugins/wazuh-core/server/controllers/index.ts deleted file mode 100644 index 616611ede1..0000000000 --- a/plugins/wazuh-core/server/controllers/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Wazuh app - Module to export all the controllers - * Copyright (C) 2015-2023 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 { WazuhHostsCtrl } from './wazuh-hosts'; diff --git a/plugins/wazuh-core/server/plugin.ts b/plugins/wazuh-core/server/plugin.ts index 5cbb0b0bd6..b165b23519 100644 --- a/plugins/wazuh-core/server/plugin.ts +++ b/plugins/wazuh-core/server/plugin.ts @@ -6,26 +6,97 @@ import { Logger, } from 'opensearch-dashboards/server'; -import { PluginSetup, WazuhCorePluginSetup, WazuhCorePluginStart } from './types'; +import { + PluginSetup, + WazuhCorePluginSetup, + WazuhCorePluginStart, +} from './types'; import { setCore } from './plugin-services'; -import * as controllers from './controllers'; -import * as services from './services'; -import { SecurityObj } from './services/security-factory'; +import { + CacheAPIUserAllowRunAs, + ManageHosts, + createDashboardSecurity, + ServerAPIClient, + ServerAPIHostEntries, + UpdateConfigurationFile, + UpdateRegistry, +} from './services'; -export class WazuhCorePlugin implements Plugin { +export class WazuhCorePlugin + implements Plugin +{ private readonly logger: Logger; + private services: { [key: string]: any }; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); + this.services = {}; } - public async setup(core: CoreSetup, plugins: PluginSetup): Promise { + public async setup( + core: CoreSetup, + plugins: PluginSetup, + ): Promise { this.logger.debug('wazuh_core: Setup'); - const wazuhSecurity = await SecurityObj(plugins); + this.services.dashboardSecurity = createDashboardSecurity(plugins); + + this.services.updateRegistry = new UpdateRegistry( + this.logger.get('update-registry'), + ); + + this.services.manageHosts = new ManageHosts( + this.logger.get('manage-hosts'), + this.services.updateRegistry, + ); + + this.services.serverAPIClient = new ServerAPIClient( + this.logger.get('server-api-client'), + this.services.manageHosts, + this.services.dashboardSecurity, + ); + + this.services.cacheAPIUserAllowRunAs = new CacheAPIUserAllowRunAs( + this.logger.get('api-user-allow-run-as'), + this.services.manageHosts, + this.services.serverAPIClient, + ); + + this.services.serverAPIHostEntries = new ServerAPIHostEntries( + this.logger.get('server-api-host-entries'), + this.services.manageHosts, + this.services.updateRegistry, + this.services.cacheAPIUserAllowRunAs, + ); + + this.services.updateConfigurationFile = new UpdateConfigurationFile( + this.logger.get('update-configuration-file'), + ); + + // Register a property to the context parameter of the endpoint handlers + core.http.registerRouteHandlerContext('wazuh_core', (context, request) => { + return { + ...this.services, + api: { + client: { + asInternalUser: this.services.serverAPIClient.asInternalUser, + asCurrentUser: this.services.serverAPIClient.asScoped( + context, + request, + ), + }, + }, + }; + }); return { - wazuhSecurity, + ...this.services, + api: { + client: { + asInternalUser: this.services.serverAPIClient.asInternalUser, + asScoped: this.services.serverAPIClient.asScoped, + }, + }, }; } @@ -35,8 +106,13 @@ export class WazuhCorePlugin implements Plugin - await ApiInterceptor.authenticate(apiHostID), - request: async (method: string, path: string, data: any, options: APIInterceptorRequestOptionsInternalUser) => - await ApiInterceptor.requestAsInternalUser( - method, - path, - data, - options, - ), - }, - }, - }; \ No newline at end of file diff --git a/plugins/wazuh-core/server/services/api-interceptor.ts b/plugins/wazuh-core/server/services/api-interceptor.ts deleted file mode 100644 index 256eaede05..0000000000 --- a/plugins/wazuh-core/server/services/api-interceptor.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Wazuh app - Interceptor API entries - * 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 axios, { AxiosResponse } from 'axios'; -import { ManageHosts } from './manage-hosts'; -import https from 'https'; - -const httpsAgent = new https.Agent({ - rejectUnauthorized: false, -}); - -const _axios = axios.create({ httpsAgent }); - -interface APIHost{ - url: string - port: string - username: string - password: string -} - -export interface APIInterceptorRequestOptions{ - apiHostID: string - token: string - forceRefresh?: boolean -} - -export interface APIInterceptorRequestOptionsInternalUser{ - apiHostID: string - forceRefresh?: boolean -} - -const manageHosts = new ManageHosts(); - -// Cache to save the token for the internal user by API host ID -const CacheInternalUserAPIHostToken = new Map(); - -export const authenticate = async (apiHostID: string, authContext?: any): Promise => { - try{ - const api: APIHost = await manageHosts.getHostById(apiHostID); - const optionsRequest = { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - auth: { - username: api.username, - password: api.password, - }, - url: `${api.url}:${api.port}/security/user/authenticate${!!authContext ? '/run_as' : ''}`, - ...(!!authContext ? { data: authContext } : {}) - }; - - const response: AxiosResponse = await _axios(optionsRequest); - const token: string = (((response || {}).data || {}).data || {}).token; - if (!authContext) { - CacheInternalUserAPIHostToken.set(apiHostID, token); - }; - return token; - }catch(error){ - throw error; - } -}; - -const buildRequestOptions = async (method: string, path: string, data: any, { apiHostID, forceRefresh, token }: APIInterceptorRequestOptions) => { - const api = await manageHosts.getHostById(apiHostID); - const { body, params, headers, ...rest } = data; - return { - method: method, - headers: { - 'content-type': 'application/json', - Authorization: 'Bearer ' + token, - ...(headers ? headers : {}) - }, - data: body || rest || {}, - params: params || {}, - url: `${api.url}:${api.port}${path}`, - } -} - -export const requestAsInternalUser = async (method: string, path: string, data: any, options: APIInterceptorRequestOptionsInternalUser) => { - try{ - const token = CacheInternalUserAPIHostToken.has(options.apiHostID) && !options.forceRefresh - ? CacheInternalUserAPIHostToken.get(options.apiHostID) - : await authenticate(options.apiHostID); - return await request(method, path, data, {...options, token}); - }catch(error){ - if (error.response && error.response.status === 401) { - try{ - const token: string = await authenticate(options.apiHostID); - return await request(method, path, data, {...options, token}); - }catch(error){ - throw error; - } - } - throw error; - } -}; - -export const requestAsCurrentUser = async (method: string, path: string, data: any, options: APIInterceptorRequestOptions) => { - return await request(method, path, data, options) -}; - -const request = async (method: string, path: string, data: any, options: any): Promise => { - try{ - const optionsRequest = await buildRequestOptions(method, path, data, options); - const response: AxiosResponse = await _axios(optionsRequest); - return response; - }catch(error){ - throw error; - } -}; diff --git a/plugins/wazuh-core/server/services/base-logger.ts b/plugins/wazuh-core/server/services/base-logger.ts deleted file mode 100644 index 55f95e9f09..0000000000 --- a/plugins/wazuh-core/server/services/base-logger.ts +++ /dev/null @@ -1,245 +0,0 @@ -import winston, { LogEntry } from 'winston'; -import fs from 'fs'; -import path from 'path'; -import { getConfiguration } from './get-configuration'; -import { createDataDirectoryIfNotExists, createLogFileIfNotExists } from './filesystem'; - -import { WAZUH_DATA_LOGS_DIRECTORY_PATH, MAX_MB_LOG_FILES } from '../../common/constants'; - -export interface IUIPlainLoggerSettings { - level: string; - message?: string; - data?: any; -} - -export interface IUILoggerSettings extends IUIPlainLoggerSettings { - date: Date; - location: string; -} - -export class BaseLogger { - allowed: boolean = false; - wazuhLogger: winston.Logger | undefined = undefined; - wazuhPlainLogger: winston.Logger | undefined = undefined; - PLAIN_LOGS_PATH: string = ''; - PLAIN_LOGS_FILE_NAME: string = ''; - RAW_LOGS_PATH: string = ''; - RAW_LOGS_FILE_NAME: string = ''; - - constructor(plainLogsFile: string, rawLogsFile: string) { - this.PLAIN_LOGS_PATH = path.join(WAZUH_DATA_LOGS_DIRECTORY_PATH, plainLogsFile); - this.RAW_LOGS_PATH = path.join(WAZUH_DATA_LOGS_DIRECTORY_PATH, rawLogsFile); - this.PLAIN_LOGS_FILE_NAME = plainLogsFile; - this.RAW_LOGS_FILE_NAME = rawLogsFile; - } - - /** - * Initialize loggers, plain and raw logger - */ - private initLogger = () => { - const configurationFile = getConfiguration(); - const level = - typeof (configurationFile || {})['logs.level'] !== 'undefined' && - ['info', 'debug'].includes(configurationFile['logs.level']) - ? configurationFile['logs.level'] - : 'info'; - - // JSON logger - this.wazuhLogger = winston.createLogger({ - level, - format: winston.format.json(), - transports: [ - new winston.transports.File({ - filename: this.RAW_LOGS_PATH, - }), - ], - }); - - // Prevents from exit on error related to the logger. - this.wazuhLogger.exitOnError = false; - - // Plain text logger - this.wazuhPlainLogger = winston.createLogger({ - level, - format: winston.format.simple(), - transports: [ - new winston.transports.File({ - filename: this.PLAIN_LOGS_PATH, - }), - ], - }); - - // Prevents from exit on error related to the logger. - this.wazuhPlainLogger.exitOnError = false; - }; - - /** - * Checks if wazuh/logs exists. If it doesn't exist, it will be created. - */ - initDirectory = async () => { - try { - createDataDirectoryIfNotExists(); - createDataDirectoryIfNotExists('logs'); - if (typeof this.wazuhLogger === 'undefined' || typeof this.wazuhPlainLogger === 'undefined') { - this.initLogger(); - } - this.allowed = true; - return; - } catch (error) { - this.allowed = false; - return Promise.reject(error); - } - }; - - /** - * Returns given file size in MB, if the file doesn't exist returns 0 - * @param {*} filename Path to the file - */ - getFilesizeInMegaBytes = (filename: string) => { - if (this.allowed) { - if (fs.existsSync(filename)) { - const stats = fs.statSync(filename); - const fileSizeInMegaBytes = stats.size; - - return fileSizeInMegaBytes / 1000000.0; - } - } - return 0; - }; - - /** - * Check if file exist - * @param filename - * @returns boolean - */ - checkFileExist = (filename: string) => { - return fs.existsSync(filename); - }; - - rotateFiles = (file: string, pathFile: string, log?: string) => { - if (this.getFilesizeInMegaBytes(pathFile) >= MAX_MB_LOG_FILES) { - const fileExtension = path.extname(file); - const fileName = path.basename(file, fileExtension); - fs.renameSync( - pathFile, - `${WAZUH_DATA_LOGS_DIRECTORY_PATH}/${fileName}-${new Date().getTime()}${fileExtension}` - ); - if (log) { - fs.writeFileSync(pathFile, log + '\n'); - } - } - }; - - /** - * Checks if the wazuhapp.log file size is greater than 100MB, if so it rotates the file. - */ - private checkFiles = () => { - createLogFileIfNotExists(this.RAW_LOGS_PATH); - createLogFileIfNotExists(this.PLAIN_LOGS_PATH); - if (this.allowed) { - // check raw log file - this.rotateFiles( - this.RAW_LOGS_FILE_NAME, - this.RAW_LOGS_PATH, - JSON.stringify({ - date: new Date(), - level: 'info', - location: 'logger', - message: 'Rotated log file', - }) - ); - // check log file - this.rotateFiles(this.PLAIN_LOGS_FILE_NAME, this.PLAIN_LOGS_PATH); - } - }; - - /** - * Get Current Date - * @returns string - */ - private yyyymmdd = () => { - const now = new Date(); - const y = now.getFullYear(); - const m = now.getMonth() + 1; - const d = now.getDate(); - const seconds = now.getSeconds(); - const minutes = now.getMinutes(); - const hour = now.getHours(); - return `${y}/${m < 10 ? '0' : ''}${m}/${d < 10 ? '0' : ''}${d} ${hour}:${minutes}:${seconds}`; - }; - - /** - * This function filter some known interfaces to avoid log hug objects - * @param data string | object - * @returns the data parsed - */ - private parseData = (data: any) => { - let parsedData = - data instanceof Error - ? { - message: data.message, - stack: data.stack, - } - : data; - - // when error is AxiosError, it extends from Error - if (data.isAxiosError) { - const { config } = data; - parsedData = { - ...parsedData, - config: { - url: config.url, - method: config.method, - data: config.data, - params: config.params, - }, - }; - } - - if (typeof parsedData === 'object') parsedData.toString = () => JSON.stringify(parsedData); - - return parsedData; - }; - - /** - * Main function to add a new log - * @param {*} location File where the log is being thrown - * @param {*} data Message or object to log - * @param {*} level Optional, default is 'error' - */ - async log(location: string, data: any, level?: string) { - const parsedData = this.parseData(data); - return this.initDirectory() - .then(() => { - if (this.allowed) { - this.checkFiles(); - const plainLogData: IUIPlainLoggerSettings = { - level: level || 'error', - message: `${this.yyyymmdd()}: ${location || 'Unknown origin'}: ${ - parsedData.toString() || 'An error occurred' - }`, - }; - - this.wazuhPlainLogger?.log(plainLogData as LogEntry); - - const logData: IUILoggerSettings = { - date: new Date(), - level: level || 'error', - location: location || 'Unknown origin', - data: parsedData || 'An error occurred', - }; - - if (typeof data == 'string') { - logData.message = parsedData; - delete logData.data; - } - - this.wazuhLogger?.log(logData as LogEntry); - } - }) - .catch((error) => { - console.error(`Cannot create the logs directory due to:\n${error.message || error}`); - throw error; - }); - } -} diff --git a/plugins/wazuh-core/server/services/cache-api-user-has-run-as.ts b/plugins/wazuh-core/server/services/cache-api-user-has-run-as.ts index 725ec3771b..7ca8b1d19e 100644 --- a/plugins/wazuh-core/server/services/cache-api-user-has-run-as.ts +++ b/plugins/wazuh-core/server/services/cache-api-user-has-run-as.ts @@ -9,88 +9,90 @@ * * Find more information about this on the LICENSE file. */ -import * as ApiInterceptor from './api-interceptor'; +import { Logger } from 'opensearch-dashboards/server'; import { ManageHosts } from './manage-hosts'; -import { log } from './logger'; -// Private variable to save the cache -const _cache = {}; +import { ServerAPIClient } from './server-api-client'; +import { API_USER_STATUS_RUN_AS } from '../../common/api-user-status-run-as'; -// Export an interface which interacts with the private cache object -export const CacheInMemoryAPIUserAllowRunAs = { +// Object.freeze(API_USER_STATUS_RUN_AS); +/** + * This service caches the status of API host internal user allows the run as option. + */ +export class CacheAPIUserAllowRunAs { + readonly API_USER_STATUS_RUN_AS; + private _cache: any; + constructor( + private logger: Logger, + private manageHosts: ManageHosts, + private serverAPIClient: ServerAPIClient, + ) { + // TODO: create API Client and replace API Interceptor + // Private variable to save the cache + this._cache = {}; + this.API_USER_STATUS_RUN_AS = API_USER_STATUS_RUN_AS; + } // Set an entry with API ID, username and allow_run_as - set: (apiID: string, username: string, allow_run_as : number): void => { - if(!_cache[apiID]){ - _cache[apiID] = {}; // Create a API ID entry if it doesn't exist in cache object - }; - _cache[apiID][username] = allow_run_as; - }, + set(apiID: string, username: string, allow_run_as: number): void { + if (!this._cache[apiID]) { + this._cache[apiID] = {}; // Create a API ID entry if it doesn't exist in cache object + } + this._cache[apiID][username] = allow_run_as; + } // Get the value of an entry with API ID and username from cache - get: (apiID: string, username: string): number => _cache[apiID] && typeof _cache[apiID][username] !== 'undefined' ? _cache[apiID][username] : API_USER_STATUS_RUN_AS.ALL_DISABLED, + get(apiID: string, username: string): number { + return this._cache[apiID] && + typeof this._cache[apiID][username] !== 'undefined' + ? this._cache[apiID][username] + : API_USER_STATUS_RUN_AS.ALL_DISABLED; + } // Check if it exists the API ID and username in the cache - has: (apiID: string, username: string): boolean => _cache[apiID] && typeof _cache[apiID][username] !== 'undefined' ? true : false -}; - -const manageHosts = new ManageHosts(); - -export const APIUserAllowRunAs = { - async check(apiId: string): Promise{ - try{ - const api = await manageHosts.getHostById(apiId); - log('APIUserAllowRunAs:check', `Check if API user ${api.username} (${apiId}) has run_as`, 'debug'); + has(apiID: string, username: string): boolean { + return this._cache[apiID] && + typeof this._cache[apiID][username] !== 'undefined' + ? true + : false; + } + async check(apiId: string): Promise { + try { + const api = await this.manageHosts.getHostById(apiId); + this.logger.debug( + `Check if API user ${api.username} (${apiId}) has run_as`, + ); // Check if api.run_as is false or undefined, then it set to false in cache - if(!api.run_as){ - CacheInMemoryAPIUserAllowRunAs.set(apiId, api.username, API_USER_STATUS_RUN_AS.HOST_DISABLED); - }; + if (!api.run_as) { + this.set(apiId, api.username, API_USER_STATUS_RUN_AS.HOST_DISABLED); + } // Check if the API user is cached and returns it - if(CacheInMemoryAPIUserAllowRunAs.has(apiId, api.username)){ - return CacheInMemoryAPIUserAllowRunAs.get(apiId, api.username); - }; - const response = await ApiInterceptor.requestAsInternalUser( + if (this.has(apiId, api.username)) { + return this.get(apiId, api.username); + } + const response = await this.serverAPIClient.asInternalUser.request( 'get', '/security/users/me', {}, - { apiHostID: apiId } + { apiHostID: apiId }, ); - const statusUserAllowRunAs = response.data.data.affected_items[0].allow_run_as ? API_USER_STATUS_RUN_AS.ENABLED : API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED; + const statusUserAllowRunAs = response.data.data.affected_items[0] + .allow_run_as + ? API_USER_STATUS_RUN_AS.ENABLED + : API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED; // Cache the run_as for the API user - CacheInMemoryAPIUserAllowRunAs.set(apiId, api.username, statusUserAllowRunAs); + this.set(apiId, api.username, statusUserAllowRunAs); return statusUserAllowRunAs; - }catch(error){ - log('APIUserAllowRunAs:check', error.message || error); + } catch (error) { + this.logger.error(error.message || error); return API_USER_STATUS_RUN_AS.ALL_DISABLED; } - }, - async canUse(apiId: string): Promise{ - const ApiUserCanUseStatus = await APIUserAllowRunAs.check(apiId); - if(ApiUserCanUseStatus === API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED){ - const api = await manageHosts.getHostById(apiId); - throw new Error(`API with host ID [${apiId}] misconfigured. The Wazuh API user [${api.username}] is not allowed to use [run_as]. Allow it in the user configuration or set [run_as] host setting with [false] value.`); + } + async canUse(apiId: string): Promise { + const ApiUserCanUseStatus = await this.check(apiId); + if (ApiUserCanUseStatus === API_USER_STATUS_RUN_AS.USER_NOT_ALLOWED) { + const api = await this.manageHosts.getHostById(apiId); + throw new Error( + `API with host ID [${apiId}] misconfigured. The Wazuh API user [${api.username}] is not allowed to use [run_as]. Allow it in the user configuration or set [run_as] host setting with [false] value.`, + ); } return ApiUserCanUseStatus; } -}; - -/** - * @example - * HOST = set in wazuh.yml config - * USER = set in user interface - * - * ALL_DISABLED - * binary 00 = decimal 0 ---> USER 0 y HOST 0 - * - * USER_NOT_ALLOWED - * binary 01 = decimal 1 ---> USER 0 y HOST 1 - * - * HOST_DISABLED - * binary 10 = decimal 2 ---> USER 1 y HOST 0 - * - * ENABLED - * binary 11 = decimal 3 ---> USER 1 y HOST 1 - */ -export enum API_USER_STATUS_RUN_AS{ - ALL_DISABLED = 0, // Wazuh HOST and USER API user configured with run_as=false or undefined - USER_NOT_ALLOWED = 1, // Wazuh HOST API user configured with run_as = TRUE in wazuh.yml but it has not run_as in Wazuh API - HOST_DISABLED = 2, // Wazuh HOST API user configured with run_as=false in wazuh.yml but it has not run_as in Wazuh API - ENABLED = 3 // Wazuh API user configured with run_as=true and allow run_as } diff --git a/plugins/main/server/lib/ui-logger.ts b/plugins/wazuh-core/server/services/cookie.ts similarity index 53% rename from plugins/main/server/lib/ui-logger.ts rename to plugins/wazuh-core/server/services/cookie.ts index d3ca58fd61..3d3beff12a 100644 --- a/plugins/main/server/lib/ui-logger.ts +++ b/plugins/wazuh-core/server/services/cookie.ts @@ -1,5 +1,5 @@ /* - * Wazuh app - Module for ui logging functions + * Wazuh app - Cookie util functions * Copyright (C) 2015-2022 Wazuh, Inc. * * This program is free software; you can redistribute it and/or modify @@ -9,10 +9,13 @@ * * Find more information about this on the LICENSE file. */ -import { BaseLogger } from './base-logger'; -import { - WAZUH_UI_LOGS_PLAIN_FILENAME, - WAZUH_UI_LOGS_RAW_FILENAME -} from '../../common/constants'; -export default new BaseLogger(WAZUH_UI_LOGS_PLAIN_FILENAME,WAZUH_UI_LOGS_RAW_FILENAME); +export const getCookieValueByName = ( + cookie: string, + name: string, +): string | undefined => { + if (!cookie) return; + const cookieRegExp = new RegExp(`.*${name}=([^;]+)`); + const [_, cookieNameValue] = cookie.match(cookieRegExp) || []; + return cookieNameValue; +}; diff --git a/plugins/wazuh-core/server/services/get-configuration.ts b/plugins/wazuh-core/server/services/get-configuration.ts index c24e633486..1ea855c75f 100644 --- a/plugins/wazuh-core/server/services/get-configuration.ts +++ b/plugins/wazuh-core/server/services/get-configuration.ts @@ -1,7 +1,9 @@ import fs from 'fs'; import yml from 'js-yaml'; -import { WAZUH_DATA_CONFIG_APP_PATH, WAZUH_CONFIGURATION_CACHE_TIME } from '../../common/constants'; -import { getSettingsDefault } from '../../common/services/settings'; +import { + WAZUH_DATA_CONFIG_APP_PATH, + WAZUH_CONFIGURATION_CACHE_TIME, +} from '../../common/constants'; let cachedConfiguration: any = null; let lastAssign: number = new Date().getTime(); @@ -15,16 +17,22 @@ export function getConfiguration(options: { force?: boolean } = {}) { try { const now = new Date().getTime(); const dateDiffer = now - lastAssign; - const defaultConfiguration = getSettingsDefault(); - if (!cachedConfiguration || dateDiffer >= WAZUH_CONFIGURATION_CACHE_TIME || options?.force) { + if ( + !cachedConfiguration || + dateDiffer >= WAZUH_CONFIGURATION_CACHE_TIME || + options?.force + ) { cachedConfiguration = obfuscateHostsConfiguration( readPluginConfigurationFile(WAZUH_DATA_CONFIG_APP_PATH), - ['password'] + ['password'], ); lastAssign = now; } - return { ...defaultConfiguration, ...cachedConfiguration }; + /* WARNING: This should only return the configuration defined in the configuration file. + Merging the default settings with the user settings could cause side effects in other services. + */ + return cachedConfiguration; } catch (error) { return false; } @@ -46,23 +54,31 @@ function readPluginConfigurationFile(filepath: string) { * @param obfuscateHostConfigurationKeys Keys to obfuscate its value in the hosts configuration. * @returns */ -function obfuscateHostsConfiguration(configuration: any, obfuscateHostConfigurationKeys: string[]) { +function obfuscateHostsConfiguration( + configuration: any, + obfuscateHostConfigurationKeys: string[], +) { if (configuration.hosts) { - configuration.hosts = configuration.hosts.map((host: { [hostID: string]: any }) => { - const hostID = Object.keys(host)[0]; - return { - [hostID]: { - ...host[hostID], - ...obfuscateHostConfigurationKeys.reduce( - (accumObfuscateHostConfigurationKeys, obfuscateHostConfigurationKey) => ({ - ...accumObfuscateHostConfigurationKeys, - [obfuscateHostConfigurationKey]: '*****', - }), - {} - ), - }, - }; - }); + configuration.hosts = configuration.hosts.map( + (host: { [hostID: string]: any }) => { + const hostID = Object.keys(host)[0]; + return { + [hostID]: { + ...host[hostID], + ...obfuscateHostConfigurationKeys.reduce( + ( + accumObfuscateHostConfigurationKeys, + obfuscateHostConfigurationKey, + ) => ({ + ...accumObfuscateHostConfigurationKeys, + [obfuscateHostConfigurationKey]: '*****', + }), + {}, + ), + }, + }; + }, + ); } return configuration; } diff --git a/plugins/wazuh-core/server/services/index.ts b/plugins/wazuh-core/server/services/index.ts index 3890cc4326..3f956dd192 100644 --- a/plugins/wazuh-core/server/services/index.ts +++ b/plugins/wazuh-core/server/services/index.ts @@ -10,6 +10,13 @@ * Find more information about this on the LICENSE file. */ -export { log } from './logger'; -export { wazuhApiClient } from './api-client'; -export * as securityFactory from './security-factory'; +export * from './cache-api-user-has-run-as'; +export * from './cookie'; +export * from './filesystem'; +export * from './get-configuration'; +export * from './manage-hosts'; +export * from './security-factory'; +export * from './server-api-client'; +export * from './server-api-host-entries'; +export * from './update-registry'; +export * from './update-configuration-file'; diff --git a/plugins/wazuh-core/server/services/logger.ts b/plugins/wazuh-core/server/services/logger.ts deleted file mode 100644 index 1f0ffb0856..0000000000 --- a/plugins/wazuh-core/server/services/logger.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseLogger } from './base-logger'; -import { - WAZUH_DATA_LOGS_PLAIN_FILENAME, - WAZUH_DATA_LOGS_RAW_FILENAME, -} from '../../common/constants'; - -const logger = new BaseLogger(WAZUH_DATA_LOGS_PLAIN_FILENAME, WAZUH_DATA_LOGS_RAW_FILENAME); - -export const log = (location: string, message: string, level?: string) => { - logger.log(location, message, level); -}; diff --git a/plugins/wazuh-core/server/services/manage-hosts.ts b/plugins/wazuh-core/server/services/manage-hosts.ts index c9450eb813..982a8d7192 100644 --- a/plugins/wazuh-core/server/services/manage-hosts.ts +++ b/plugins/wazuh-core/server/services/manage-hosts.ts @@ -11,21 +11,22 @@ */ import fs from 'fs'; import yml from 'js-yaml'; -import { log } from './logger'; import { UpdateRegistry } from './update-registry'; import { initialWazuhConfig } from './initial-wazuh-config'; import { WAZUH_DATA_CONFIG_APP_PATH } from '../../common/constants'; import { createDataDirectoryIfNotExists } from './filesystem'; +import { Logger } from 'opensearch-dashboards/server'; +/** + * This services manages the API host entries + */ export class ManageHosts { busy: boolean; file: string; - updateRegistry: UpdateRegistry; initialConfig: string; - constructor() { + constructor(private logger: Logger, private updateRegistry: UpdateRegistry) { this.busy = false; this.file = WAZUH_DATA_CONFIG_APP_PATH; - this.updateRegistry = new UpdateRegistry(); this.initialConfig = initialWazuhConfig; } @@ -36,14 +37,14 @@ export class ManageHosts { */ composeHost(host, id) { try { - log('manage-hosts:composeHost', 'Composing host', 'debug'); + this.logger.debug('Composing host'); return ` - ${!id ? new Date().getTime() : id}: url: ${host.url} port: ${host.port} username: ${host.username || host.user} password: ${host.password}`; } catch (error) { - log('manage-hosts:composeHost', error.message || error); + this.logger.error(error.message || error); throw error; } } @@ -56,10 +57,10 @@ export class ManageHosts { try { const hostId = Object.keys(host)[0]; const reg = `\\s*-\\s*${hostId}\\s*:\\s*\\n*\\s*url\\s*:\\s*\\S*\\s*\\n*\\s*port\\s*:\\s*\\S*\\s*\\n*\\s*username\\s*:\\s*\\S*\\s*\\n*\\s*password\\s*:\\s*\\S*`; - log('manage-hosts:composeRegex', 'Composing regex', 'debug'); + this.logger.debug('Composing regex'); return new RegExp(`${reg}`, 'gm'); } catch (error) { - log('manage-hosts:composeRegex', error.message || error); + this.logger.error(error.message || error); throw error; } } @@ -82,12 +83,12 @@ export class ManageHosts { const raw = fs.readFileSync(this.file, { encoding: 'utf-8' }); this.busy = false; const content = yml.load(raw); - log('manage-hosts:getHosts', 'Getting hosts', 'debug'); + this.logger.debug('Getting hosts'); const entries = (content || {})['hosts'] || []; return entries; } catch (error) { this.busy = false; - log('manage-hosts:getHosts', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -97,15 +98,15 @@ export class ManageHosts { */ async checkIfHostsKeyExists() { try { - log('manage-hosts:checkIfHostsKeyExists', 'Checking hosts key', 'debug'); + this.logger.debug('Checking hosts key'); this.busy = true; const raw = fs.readFileSync(this.file, { encoding: 'utf-8' }); this.busy = false; const content = yml.load(raw); return Object.keys(content || {}).includes('hosts'); } catch (error) { - log('manage-hosts:checkIfHostsKeyExists', error.message || error); this.busy = false; + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -119,10 +120,10 @@ export class ManageHosts { const ids = hosts.map(h => { return Object.keys(h)[0]; }); - log('manage-hosts:getCurrentHostsIds', 'Getting hosts ids', 'debug'); + this.logger.debug('Getting hosts ids'); return ids; } catch (error) { - log('manage-hosts:getCurrentHostsIds', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -133,7 +134,7 @@ export class ManageHosts { */ async getHostById(id) { try { - log('manage-hosts:getHostById', `Getting host ${id}`, 'debug'); + this.logger.debug(`Getting host ${id}`); const hosts = await this.getHosts(); const host = hosts.filter(h => { return Object.keys(h)[0] == id; @@ -145,7 +146,7 @@ export class ManageHosts { const result = Object.assign(host[0][key], { id: key }) || {}; return result; } catch (error) { - log('manage-hosts:getHostById', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -179,13 +180,9 @@ export class ManageHosts { }; entries.push(api); }); - log( - 'manage-hosts:transformIndexedApis', - 'Transforming index API schedule to wazuh.yml', - 'debug', - ); + this.logger.debug('Transforming index API schedule to wazuh.yml'); } catch (error) { - log('manage-hosts:transformIndexedApis', error.message || error); + this.logger.error(error.message || error); throw error; } return entries; @@ -200,7 +197,7 @@ export class ManageHosts { const apis = this.transformIndexedApis(apiEntries); return await this.addSeveralHosts(apis); } catch (error) { - log('manage-hosts:migrateFromIndex', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -215,14 +212,10 @@ export class ManageHosts { const cleanHosts = hosts.filter(h => { return !currentHosts.includes(h.id); }); - log( - 'manage-hosts:cleanExistingHosts', - 'Preventing add existings hosts', - 'debug', - ); + this.logger.debug('Preventing add existings hosts'); return cleanHosts; } catch (error) { - log('manage-hosts:cleanExistingHosts', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -241,7 +234,7 @@ export class ManageHosts { */ async addSeveralHosts(hosts) { try { - log('manage-hosts:addSeveralHosts', 'Adding several', 'debug'); + this.logger.debug('Adding several'); const hostsToAdd = await this.cleanExistingHosts(hosts); if (!hostsToAdd.length) return 'There are not APIs entries to migrate'; for (let idx in hostsToAdd) { @@ -250,7 +243,7 @@ export class ManageHosts { } return 'All APIs entries were migrated to the wazuh.yml'; } catch (error) { - log('manage-hosts:addSeveralHosts', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -294,11 +287,11 @@ export class ManageHosts { host.cluster_info, host.extensions, ); - log('manage-hosts:addHost', `Host ${id} was properly added`, 'debug'); + this.logger.debug(`Host ${id} was properly added`); return id; } catch (error) { this.busy = false; - log('manage-hosts:addHost', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -336,15 +329,11 @@ export class ManageHosts { } } this.busy = false; - log( - 'manage-hosts:deleteHost', - `Host ${req.params.id} was properly deleted`, - 'debug', - ); + this.logger.debug(`Host ${req.params.id} was properly deleted`); return true; } catch (error) { this.busy = false; - log('manage-hosts:deleteHost', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -374,15 +363,11 @@ export class ManageHosts { await fs.writeFileSync(this.file, result, 'utf8'); } this.busy = false; - log( - 'manage-hosts:updateHost', - `Host ${id} was properly updated`, - 'debug', - ); + this.logger.debug(`Host ${id} was properly updated`); return true; } catch (error) { this.busy = false; - log('manage-hosts:updateHost', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } diff --git a/plugins/wazuh-core/server/services/security-factory/factories/opensearch-dashboards-security-factory.ts b/plugins/wazuh-core/server/services/security-factory/factories/opensearch-dashboards-security-factory.ts index ba290fbba3..4c16eda892 100644 --- a/plugins/wazuh-core/server/services/security-factory/factories/opensearch-dashboards-security-factory.ts +++ b/plugins/wazuh-core/server/services/security-factory/factories/opensearch-dashboards-security-factory.ts @@ -1,21 +1,28 @@ import { ISecurityFactory } from '..'; -import { OpenSearchDashboardsRequest, RequestHandlerContext } from 'opensearch-dashboards/server'; +import { + OpenSearchDashboardsRequest, + RequestHandlerContext, +} from 'opensearch-dashboards/server'; import md5 from 'md5'; import { WAZUH_SECURITY_PLUGIN_OPENSEARCH_DASHBOARDS_SECURITY } from '../../../../common/constants'; export class OpenSearchDashboardsSecurityFactory implements ISecurityFactory { platform: string = WAZUH_SECURITY_PLUGIN_OPENSEARCH_DASHBOARDS_SECURITY; - async getCurrentUser(request: OpenSearchDashboardsRequest, context: RequestHandlerContext) { + async getCurrentUser( + request: OpenSearchDashboardsRequest, + context: RequestHandlerContext, + ) { try { const params = { path: `/_opendistro/_security/api/account`, method: 'GET', }; - const { - body: authContext, - } = await context.core.opensearch.client.asCurrentUser.transport.request(params); + const { body: authContext } = + await context.core.opensearch.client.asCurrentUser.transport.request( + params, + ); const username = this.getUserName(authContext); return { username, authContext, hashUsername: md5(username) }; } catch (error) { diff --git a/plugins/wazuh-core/server/services/security-factory/index.ts b/plugins/wazuh-core/server/services/security-factory/index.ts index 629d004a60..1f800fdb7f 100644 --- a/plugins/wazuh-core/server/services/security-factory/index.ts +++ b/plugins/wazuh-core/server/services/security-factory/index.ts @@ -1 +1 @@ -export { ISecurityFactory, SecurityObj} from './security-factory'; \ No newline at end of file +export * from './security-factory'; diff --git a/plugins/wazuh-core/server/services/security-factory/security-factory.ts b/plugins/wazuh-core/server/services/security-factory/security-factory.ts index f3168bb1c4..6d6e1b3f6a 100644 --- a/plugins/wazuh-core/server/services/security-factory/security-factory.ts +++ b/plugins/wazuh-core/server/services/security-factory/security-factory.ts @@ -1,5 +1,11 @@ -import { OpenSearchDashboardsSecurityFactory, DefaultFactory } from './factories'; -import { OpenSearchDashboardsRequest, RequestHandlerContext } from 'src/core/server'; +import { + OpenSearchDashboardsSecurityFactory, + DefaultFactory, +} from './factories'; +import { + OpenSearchDashboardsRequest, + RequestHandlerContext, +} from 'src/core/server'; import { PluginSetup } from '../../types'; type CurrentUser = { @@ -11,10 +17,14 @@ export interface ISecurityFactory { platform?: string; getCurrentUser( request: OpenSearchDashboardsRequest, - context?: RequestHandlerContext + context?: RequestHandlerContext, ): Promise; } -export async function SecurityObj({ securityDashboards }: PluginSetup): Promise { - return !!securityDashboards ? new OpenSearchDashboardsSecurityFactory() : new DefaultFactory(); +export function createDashboardSecurity({ + securityDashboards, +}: PluginSetup): Promise { + return !!securityDashboards + ? new OpenSearchDashboardsSecurityFactory() + : new DefaultFactory(); } diff --git a/plugins/wazuh-core/server/services/server-api-client.ts b/plugins/wazuh-core/server/services/server-api-client.ts new file mode 100644 index 0000000000..4dff19825d --- /dev/null +++ b/plugins/wazuh-core/server/services/server-api-client.ts @@ -0,0 +1,249 @@ +/* + * Wazuh app - Interceptor API entries + * 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 axios, { AxiosInstance, AxiosResponse } from 'axios'; +import https from 'https'; +import { Logger } from 'opensearch-dashboards/server'; +import { getCookieValueByName } from './cookie'; +import { ManageHosts } from './manage-hosts'; +import { ISecurityFactory } from './security-factory'; + +interface APIHost { + url: string; + port: string; + username: string; + password: string; +} + +type RequestHTTPMethod = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT'; +type RequestPath = string; + +export interface APIInterceptorRequestOptions { + apiHostID: string; + token: string; + forceRefresh?: boolean; +} + +export interface APIInterceptorRequestOptionsInternalUser { + apiHostID: string; + forceRefresh?: boolean; +} + +export interface APIInterceptorRequestOptionsScopedUser { + apiHostID: string; + forceRefresh?: boolean; + token: string; +} + +export interface ServerAPIInternalUserClient { + authenticate: (apiHostID: string) => Promise; + request: ( + method: RequestHTTPMethod, + path: RequestPath, + data: any, + options: APIInterceptorRequestOptionsInternalUser, + ) => Promise>; +} + +export interface ServerAPIScopedUserClient { + authenticate: (apiHostID: string) => Promise; + request: ( + method: RequestHTTPMethod, + path: RequestPath, + data: any, + options: APIInterceptorRequestOptionsScopedUser, + ) => Promise>; +} + +/** + * This service communicates with the Wazuh server APIs + */ +export class ServerAPIClient { + private _CacheInternalUserAPIHostToken: Map; + private _axios: typeof axios; + private asInternalUser: ServerAPIInternalUserClient; + private _axios: AxiosInstance; + constructor( + private logger: Logger, // TODO: add logger as needed + private manageHosts: ManageHosts, + private dashboardSecurity: ISecurityFactory, + ) { + const httpsAgent = new https.Agent({ + rejectUnauthorized: false, + }); + this._axios = axios.create({ httpsAgent }); + // Cache to save the token for the internal user by API host ID + this._CacheInternalUserAPIHostToken = new Map(); + + // Create internal user client + this.asInternalUser = { + authenticate: async apiHostID => await this._authenticate(apiHostID), + request: async ( + method: RequestHTTPMethod, + path: RequestPath, + data: any, + options, + ) => await this._requestAsInternalUser(method, path, data, options), + }; + } + + /** + * Internal method to execute the request + * @param method HTTP verb + * @param path Server API endpoint + * @param data Request data + * @param options Options. Data about the Server API ID and the token + * @returns + */ + private async _request( + method: RequestHTTPMethod, + path: RequestPath, + data: any, + options: + | APIInterceptorRequestOptionsInternalUser + | APIInterceptorRequestOptionsScopedUser, + ): Promise { + const optionsRequest = await this._buildRequestOptions( + method, + path, + data, + options, + ); + return await this._axios(optionsRequest); + } + + /** + * Build the options for the request + * @param method HTTP verb + * @param path Server API endpoint + * @param data Request data + * @param options Options. Data about the Server API ID and the token + * @returns + */ + private async _buildRequestOptions( + method: RequestHTTPMethod, + path: RequestPath, + data: any, + { apiHostID, token }: APIInterceptorRequestOptions, + ) { + const api = await this.manageHosts.getHostById(apiHostID); + const { body, params, headers, ...rest } = data; + return { + method: method, + headers: { + 'content-type': 'application/json', + Authorization: 'Bearer ' + token, + ...(headers ? headers : {}), + }, + data: body || rest || {}, + params: params || {}, + url: `${api.url}:${api.port}${path}`, + }; + } + + /** + * Get the authentication token + * @param apiHostID Server API ID + * @param authContext Authentication context to get the token + * @returns + */ + private async _authenticate( + apiHostID: string, + authContext?: any, + ): Promise { + const api: APIHost = await this.manageHosts.getHostById(apiHostID); + const optionsRequest = { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + auth: { + username: api.username, + password: api.password, + }, + url: `${api.url}:${api.port}/security/user/authenticate${ + !!authContext ? '/run_as' : '' + }`, + ...(!!authContext ? { data: authContext } : {}), + }; + + const response: AxiosResponse = await this._axios(optionsRequest); + const token: string = (((response || {}).data || {}).data || {}).token; + if (!authContext) { + this._CacheInternalUserAPIHostToken.set(apiHostID, token); + } + return token; + } + + /** + * Create a client from the context and request + * @param context + * @param request + * @returns + */ + asScoped(context: any, request: any): ServerAPIScopedUserClient { + return { + authenticate: async (apiHostID: string) => + await this._authenticate( + apiHostID, + ( + await this.dashboardSecurity.getCurrentUser(request, context) + ).authContext, + ), + request: async ( + method: RequestHTTPMethod, + path: string, + data: any, + options: APIInterceptorRequestOptionsScopedUser, + ) => { + return await this._request(method, path, data, { + ...options, + token: getCookieValueByName(request.headers.cookie, 'wz-token'), + }); + }, + }; + } + + /** + * Request as internal user + * @param method HTTP verb + * @param path Server API endpoint + * @param data Request data + * @param options Options. Data about the Server API ID and the token + * @returns + */ + private async _requestAsInternalUser( + method: RequestHTTPMethod, + path: RequestPath, + data: any, + options: APIInterceptorRequestOptionsInternalUser, + ) { + try { + const token = + this._CacheInternalUserAPIHostToken.has(options.apiHostID) && + !options.forceRefresh + ? this._CacheInternalUserAPIHostToken.get(options.apiHostID) + : await this._authenticate(options.apiHostID); + return await this._request(method, path, data, { ...options, token }); + } catch (error) { + if (error.response && error.response.status === 401) { + try { + const token: string = await this._authenticate(options.apiHostID); + return await this._request(method, path, data, { ...options, token }); + } catch (error) { + throw error; + } + } + throw error; + } + } +} diff --git a/plugins/wazuh-core/server/controllers/wazuh-hosts.ts b/plugins/wazuh-core/server/services/server-api-host-entries.ts similarity index 81% rename from plugins/wazuh-core/server/controllers/wazuh-hosts.ts rename to plugins/wazuh-core/server/services/server-api-host-entries.ts index 864caa15cd..5d2b6f13bf 100644 --- a/plugins/wazuh-core/server/controllers/wazuh-hosts.ts +++ b/plugins/wazuh-core/server/services/server-api-host-entries.ts @@ -16,18 +16,21 @@ import { PLUGIN_PLATFORM_NAME, WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH, } from '../../common/constants'; -import { APIUserAllowRunAs } from '../services/cache-api-user-has-run-as'; -import { log } from '../services/logger'; -import { ManageHosts } from '../services/manage-hosts'; -import { UpdateRegistry } from '../services/update-registry'; +import { CacheAPIUserAllowRunAs } from './cache-api-user-has-run-as'; +import { ManageHosts } from './manage-hosts'; +import { UpdateRegistry } from './update-registry'; +import { Logger } from 'opensearch-dashboards/server'; -export class WazuhHostsCtrl { - manageHosts: ManageHosts; - updateRegistry: UpdateRegistry; - constructor() { - this.manageHosts = new ManageHosts(); - this.updateRegistry = new UpdateRegistry(); - } +/** + * This service gets information about the API host entries + */ +export class ServerAPIHostEntries { + constructor( + private logger: Logger, + private manageHosts: ManageHosts, + private updateRegistry: UpdateRegistry, + private cacheAPIUserAllowRunAs: CacheAPIUserAllowRunAs, + ) {} /** * This get all hosts entries in the wazuh.yml and the related info in the wazuh-registry.json @@ -59,7 +62,7 @@ export class WazuhHostsCtrl { throw new Error(`Error getting the hosts entries: The \'${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH}\' directory could not exist in your ${PLUGIN_PLATFORM_NAME} installation. If this doesn't exist, create it and give the permissions 'sudo mkdir ${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH};sudo chown -R ${PLUGIN_PLATFORM_INSTALLATION_USER}:${PLUGIN_PLATFORM_INSTALLATION_USER_GROUP} ${WAZUH_DATA_PLUGIN_PLATFORM_BASE_ABSOLUTE_PATH}'. After, restart the ${PLUGIN_PLATFORM_NAME} service.`); } - log('wazuh-hosts:getHostsEntries', error.message || error); + this.logger.error(error); throw new Error(error); } } @@ -70,7 +73,7 @@ export class WazuhHostsCtrl { * @param {Object} registry * @param {Boolean} removePassword */ - async joinHostRegistry( + private async joinHostRegistry( hosts: any, registry: any, removePassword: boolean = true, @@ -86,7 +89,7 @@ export class WazuhHostsCtrl { const api = Object.assign(h[id], { id: id }); const host = Object.assign(api, registry[id]); // Add to run_as from API user. Use the cached value or get it doing a request - host.allow_run_as = await APIUserAllowRunAs.check(id); + host.allow_run_as = await this.cacheAPIUserAllowRunAs.check(id); if (removePassword) { delete host.password; delete host.token; diff --git a/plugins/main/server/lib/update-configuration.ts b/plugins/wazuh-core/server/services/update-configuration-file.ts similarity index 69% rename from plugins/main/server/lib/update-configuration.ts rename to plugins/wazuh-core/server/services/update-configuration-file.ts index 0c9adb84d2..2477e9e85e 100644 --- a/plugins/main/server/lib/update-configuration.ts +++ b/plugins/wazuh-core/server/services/update-configuration-file.ts @@ -10,13 +10,16 @@ * Find more information about this on the LICENSE file. */ import fs from 'fs'; -import { log } from './logger'; import { getConfiguration } from './get-configuration'; import { WAZUH_DATA_CONFIG_APP_PATH } from '../../common/constants'; import { formatSettingValueToFile } from '../../common/services/settings'; +import { Logger } from 'opensearch-dashboards/server'; +/** + * This service updates the configuration file + */ export class UpdateConfigurationFile { - constructor() { + constructor(private logger: Logger) { this.busy = false; this.file = WAZUH_DATA_CONFIG_APP_PATH; } @@ -29,17 +32,18 @@ export class UpdateConfigurationFile { */ updateLine(key, value, exists = false) { try { + this.logger.debug(`Updating setting: ${key} with value ${value}`); const data = fs.readFileSync(this.file, { encoding: 'utf-8' }); const re = new RegExp(`^${key}\\s{0,}:\\s{1,}.*`, 'gm'); const formatedValue = formatSettingValueToFile(value); const result = exists ? data.replace(re, `${key}: ${formatedValue}`) : `${data}\n${key}: ${formatedValue}`; + this.logger.info(`updateLine: ${result}`); fs.writeFileSync(this.file, result, 'utf8'); - log('update-configuration:updateLine', 'Updating line', 'debug'); return true; } catch (error) { - log('update-configuration:updateLine', error.message || error); + this.logger.error(error.message || error); throw error; } } @@ -55,24 +59,28 @@ export class UpdateConfigurationFile { } this.busy = true; - const pluginConfiguration = getConfiguration({force: true}) || {}; + const pluginConfiguration = getConfiguration({ force: true }) || {}; - for(const pluginSettingKey in updatedConfiguration){ + for (const pluginSettingKey in updatedConfiguration) { // Store the configuration in the configuration file. const value = updatedConfiguration[pluginSettingKey]; - this.updateLine(pluginSettingKey, value, typeof pluginConfiguration[pluginSettingKey] !== 'undefined'); + this.updateLine( + pluginSettingKey, + value, + /* WARNING: This is trusting the result of getConfiguration service only returns the + settings defined in the configuration file. The updateLine function could be enhanced to + identify if the setting is defined or not itself. + */ + typeof pluginConfiguration[pluginSettingKey] !== 'undefined', + ); // Update the app configuration server-cached setting in memory with the new value. pluginConfiguration[pluginSettingKey] = value; - }; + } this.busy = false; - log( - 'update-configuration:updateConfiguration', - 'Updating configuration', - 'debug' - ); + this.logger.debug('Updating configuration'); } catch (error) { - log('update-configuration:updateConfiguration', error.message || error); + this.logger.error(error.message || error); this.busy = false; throw error; } diff --git a/plugins/wazuh-core/server/services/update-registry.ts b/plugins/wazuh-core/server/services/update-registry.ts index 71e76ca627..df9c5270d5 100644 --- a/plugins/wazuh-core/server/services/update-registry.ts +++ b/plugins/wazuh-core/server/services/update-registry.ts @@ -10,13 +10,16 @@ * Find more information about this on the LICENSE file. */ import fs from 'fs'; -import { log } from './logger'; import { WAZUH_DATA_CONFIG_REGISTRY_PATH } from '../../common/constants'; +import { Logger } from 'opensearch-dashboards/server'; +/** + * This service updates the registry file + */ export class UpdateRegistry { busy: boolean; file: string; - constructor() { + constructor(private logger: Logger) { this.busy = false; this.file = WAZUH_DATA_CONFIG_REGISTRY_PATH; } @@ -26,11 +29,11 @@ export class UpdateRegistry { */ async readContent() { try { - log('update-registry:readContent', 'Reading wazuh-registry.json content', 'debug'); + this.logger.debug('Reading registry file'); const content = await fs.readFileSync(this.file, { encoding: 'utf-8' }); return JSON.parse(content); } catch (error) { - log('update-registry:readContent', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -40,11 +43,11 @@ export class UpdateRegistry { */ async getHosts() { try { - log('update-registry:getHosts', 'Getting hosts from registry', 'debug'); + this.logger.debug('Getting hosts from registry', 'debug'); const content = await this.readContent(); return content.hosts || {}; } catch (error) { - log('update-registry:getHosts', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -59,7 +62,7 @@ export class UpdateRegistry { const hosts = await this.getHosts(); return hosts.id || {}; } catch (error) { - log('update-registry:getClusterInfoByAPI', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -70,7 +73,7 @@ export class UpdateRegistry { */ async writeContent(content) { try { - log('update-registry:writeContent', 'Writting wazuh-registry.json content', 'debug'); + this.logger.debug('Writting wazuh-registry.json content'); if (this.busy) { throw new Error('Another process is updating the registry file'); } @@ -78,7 +81,7 @@ export class UpdateRegistry { await fs.writeFileSync(this.file, JSON.stringify(content)); this.busy = false; } catch (error) { - log('update-registry:writeContent', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -92,7 +95,7 @@ export class UpdateRegistry { try { return Object.keys(hosts).includes(id); } catch (error) { - log('update-registry:checkHost', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -106,14 +109,15 @@ export class UpdateRegistry { async migrateToRegistry(id, clusterInfo, clusterExtensions) { try { const content = await this.readContent(); - if (!Object.keys(content).includes('hosts')) Object.assign(content, { hosts: {} }); + if (!Object.keys(content).includes('hosts')) + Object.assign(content, { hosts: {} }); const info = { cluster_info: clusterInfo, extensions: clusterExtensions }; content.hosts[id] = info; await this.writeContent(content); - log('update-registry:migrateToRegistry', `API ${id} was properly migrated`, 'debug'); + this.logger.info(`API ${id} was properly migrated`); return info; } catch (error) { - log('update-registry:migrateToRegistry', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -130,14 +134,10 @@ export class UpdateRegistry { if (!content.hosts[id]) content.hosts[id] = {}; content.hosts[id].cluster_info = clusterInfo; await this.writeContent(content); - log( - 'update-registry:updateClusterInfo', - `API ${id} information was properly updated`, - 'debug' - ); + this.logger.debug(`API ${id} information was properly updated`); return id; } catch (error) { - log('update-registry:updateClusterInfo', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -150,16 +150,12 @@ export class UpdateRegistry { async updateAPIExtensions(id, extensions) { try { const content = await this.readContent(); - if(content.hosts[id]) content.hosts[id].extensions = extensions; + if (content.hosts[id]) content.hosts[id].extensions = extensions; await this.writeContent(content); - log( - 'update-registry:updateAPIExtensions', - `API ${id} extensions were properly updated`, - 'debug' - ); + this.logger.info(`API ${id} extensions were properly updated`); return id; } catch (error) { - log('update-registry:updateAPIHostname', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -170,12 +166,12 @@ export class UpdateRegistry { */ async removeHostEntries(ids) { try { - log('update-registry:removeHostEntry', 'Removing entry', 'debug'); + this.logger.debug('Removing entry'); const content = await this.readContent(); ids.forEach(id => delete content.hosts[id]); await this.writeContent(content); } catch (error) { - log('update-registry:removeHostEntry', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -186,7 +182,7 @@ export class UpdateRegistry { */ async removeOrphanEntries(hosts) { try { - log('update-registry:removeOrphanEntries', 'Checking orphan registry entries', 'debug'); + this.logger.debug('Checking orphan registry entries'); const entries = await this.getHosts(); const hostsKeys = hosts.map(h => { return h.id; @@ -197,7 +193,7 @@ export class UpdateRegistry { }); await this.removeHostEntries(diff); } catch (error) { - log('update-registry:removeOrphanEntries', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -212,7 +208,7 @@ export class UpdateRegistry { const hosts = await this.getHosts(); return hosts[id] ? hosts[id].token || null : null; } catch (error) { - log('update-registry:getTokenById', error.message || error); + this.logger.error(error.message || error); return Promise.reject(error); } } @@ -229,10 +225,10 @@ export class UpdateRegistry { if (!content.hosts[id]) content.hosts[id] = {}; content.hosts[id].token = token; await this.writeContent(content); - log('update-registry:updateToken', `API ${id} information was properly updated`, 'debug'); + this.logger.info(`API ${id} information was properly updated`); return id; } catch (error) { - log('update-registry:updateToken', error.message || error); + this.logger.debug(error.message || error); return Promise.reject(error); } } diff --git a/plugins/wazuh-core/server/types.ts b/plugins/wazuh-core/server/types.ts index 8d5cedb7f4..0e74a96ce0 100644 --- a/plugins/wazuh-core/server/types.ts +++ b/plugins/wazuh-core/server/types.ts @@ -1,31 +1,44 @@ -import { AxiosResponse } from 'axios'; -import { APIInterceptorRequestOptionsInternalUser } from './services/api-interceptor'; -import { WazuhHostsCtrl } from './controllers'; -import { ISecurityFactory } from './services/security-factory'; +import { + CacheAPIUserAllowRunAs, + ISecurityFactory, + ManageHosts, + ServerAPIClient, + ServerAPIHostEntries, + ServerAPIInternalUserClient, + ServerAPIScopedUserClient, + UpdateConfigurationFile, + UpdateRegistry, +} from './services'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface WazuhCorePluginSetup { - wazuhSecurity: ISecurityFactory; + dashboardSecurity: ISecurityFactory; + cacheAPIUserAllowRunAs: CacheAPIUserAllowRunAs; + manageHosts: ManageHosts; + serverAPIClient: ServerAPIClient; + serverAPIHostEntries: ServerAPIHostEntries; + updateRegistry: UpdateRegistry; + updateConfigurationFile: UpdateConfigurationFile; + api: { + client: { + asInternalUser: ServerAPIInternalUserClient; + asScoped: (context: any, request: any) => ServerAPIScopedUserClient; + }; + }; } // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface WazuhCorePluginStart { - controllers: { - WazuhHostsCtrl: typeof WazuhHostsCtrl; - }; - services: { - log: (location: string, message: string, level?: string) => void; - wazuhApiClient: { - client: { - asInternalUser: { - authenticate: (apiHostID: string) => Promise; - request: ( - method: string, - path: string, - data: any, - options: APIInterceptorRequestOptionsInternalUser - ) => Promise>; - }; - }; + dashboardSecurity: ISecurityFactory; + cacheAPIUserAllowRunAs: CacheAPIUserAllowRunAs; + manageHosts: ManageHosts; + serverAPIClient: ServerAPIClient; + serverAPIHostEntries: ServerAPIHostEntries; + updateRegistry: UpdateRegistry; + updateConfigurationFile: UpdateConfigurationFile; + api: { + client: { + asInternalUser: ServerAPIInternalUserClient; + asScoped: (context: any, request: any) => ServerAPIScopedUserClient; }; }; } diff --git a/plugins/wazuh-core/yarn.lock b/plugins/wazuh-core/yarn.lock index 10107cf612..1fca6c2895 100644 --- a/plugins/wazuh-core/yarn.lock +++ b/plugins/wazuh-core/yarn.lock @@ -2,20 +2,6 @@ # yarn lockfile v1 -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - "@testing-library/user-event@^14.5.0": version "14.5.0" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.0.tgz#4036add379525b635a64bce4d727820d4ba516a7" @@ -30,16 +16,6 @@ resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.2.tgz#529bb3f8a7e9e9f621094eb76a443f585d882528" integrity sha512-v+JFDu96+UYJ3/UWzB0mEglIS//MZXgRaJ4ubUPwOM0gvLc/kcQ3TWNYwENEK7/EcXGQVrW8h/XqednSjBd/Og== -"@types/triple-beam@^1.3.2": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.3.tgz#726ae98a5f6418c8f24f9b0f2a9f81a8664876ae" - integrity sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g== - -async@^3.2.3: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -59,47 +35,6 @@ charenc@0.0.2: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== -color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -122,21 +57,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - follow-redirects@^1.15.0: version "1.15.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" @@ -151,26 +71,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - json2csv@^4.1.2: version "4.5.4" resolved "https://registry.yarnpkg.com/json2csv/-/json2csv-4.5.4.tgz#2b59c2869a137ec48cd2e243e0180466155f773f" @@ -190,28 +95,11 @@ jwt-decode@^3.1.2: resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== -logform@^2.3.2, logform@^2.4.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.5.1.tgz#44c77c34becd71b3a42a3970c77929e52c6ed48b" - integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg== - dependencies: - "@colors/colors" "1.5.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -233,11 +121,6 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - node-cron@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" @@ -245,98 +128,12 @@ node-cron@^3.0.2: dependencies: uuid "8.3.2" -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-stable-stringify@^2.3.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - uuid@8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== - dependencies: - logform "^2.3.2" - readable-stream "^3.6.0" - triple-beam "^1.3.0" - -winston@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.10.0.tgz#d033cb7bd3ced026fed13bf9d92c55b903116803" - integrity sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g== - dependencies: - "@colors/colors" "1.5.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.4.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.5.0" From e649ae25c02ab4ca20fd49cdcbf86fd5be577ffe Mon Sep 17 00:00:00 2001 From: Luciano Gorza <103193307+lucianogorza@users.noreply.github.com> Date: Thu, 28 Dec 2023 15:01:15 -0300 Subject: [PATCH 014/136] Allow edit groups from an agent (#6250) * Agent groups column refactor and allow remove * Agent groups allow edit * Edit agent groups modal * Fix links las registered agent * Agent table with actions * Extend funtionality from button with permissions and restore groups trucate component * Modularize agents table actions * Fix table filters * Add form modal message * Add CHANGELOG * Update snapshot * Fix return * Fix actions field * Fix snapshot * Disable 'View details' actions when agent never connected * Delete 'Upgrade' action --- CHANGELOG.md | 1 + .../components/common/permissions/button.tsx | 115 ++-- .../components/common/permissions/element.tsx | 109 ++++ .../components/common/permissions/prompt.tsx | 2 +- .../agent_group_truncate.tsx | 81 --- .../agent-group-truncate/group-truncate.tsx | 8 +- .../common/util/agent-group-truncate/index.ts | 3 +- .../public/components/common/util/index.ts | 10 +- .../components/common/welcome/agents-info.js | 1 - .../endpoints-summary/endpoints-summary.tsx | 54 +- .../endpoints-summary/hooks/agents.ts | 31 ++ .../endpoints-summary/hooks/endpoints.ts | 37 -- .../endpoints-summary/hooks/groups.ts | 32 ++ .../endpoints-summary/hooks/index.ts | 2 +- .../components/endpoints-summary/index.tsx | 11 +- .../services/add-agent-to-group.tsx | 4 + .../endpoints-summary/services/get-groups.tsx | 8 + .../services/get-total-agents.tsx | 12 + .../endpoints-summary/services/index.tsx | 5 + .../services/remove-agent-from-group.tsx | 6 + .../services/remove-agent-from-groups.tsx | 11 + .../__snapshots__/agents-table.test.tsx.snap | 6 - .../endpoints-summary/table/actions.tsx | 81 +++ .../endpoints-summary/table/agents-table.js | 523 ------------------ .../endpoints-summary/table/agents-table.tsx | 484 ++++++++++++++++ .../table/edit-groups-modal.tsx | 155 ++++++ 26 files changed, 1032 insertions(+), 760 deletions(-) create mode 100644 plugins/main/public/components/common/permissions/element.tsx delete mode 100644 plugins/main/public/components/common/util/agent-group-truncate/agent_group_truncate.tsx create mode 100644 plugins/main/public/components/endpoints-summary/hooks/agents.ts delete mode 100644 plugins/main/public/components/endpoints-summary/hooks/endpoints.ts create mode 100644 plugins/main/public/components/endpoints-summary/hooks/groups.ts create mode 100644 plugins/main/public/components/endpoints-summary/services/add-agent-to-group.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/get-groups.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/get-total-agents.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/index.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/remove-agent-from-group.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/remove-agent-from-groups.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/actions.tsx delete mode 100644 plugins/main/public/components/endpoints-summary/table/agents-table.js create mode 100644 plugins/main/public/components/endpoints-summary/table/agents-table.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/edit-groups-modal.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index f67cd225da..ce55f82853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145) - Remove embedded discover [#6120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6120) - 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) ## Wazuh v4.8.1 - OpenSearch Dashboards 2.10.0 - Revision 00 diff --git a/plugins/main/public/components/common/permissions/button.tsx b/plugins/main/public/components/common/permissions/button.tsx index 8bfa0b34c6..799f5539ae 100644 --- a/plugins/main/public/components/common/permissions/button.tsx +++ b/plugins/main/public/components/common/permissions/button.tsx @@ -10,9 +10,7 @@ * Find more information about this on the LICENSE file. */ -import React, { Fragment } from 'react'; -import { useUserPermissionsRequirements } from '../hooks/useUserPermissions'; -import { useUserRolesRequirements } from '../hooks/useUserRoles'; +import React from 'react'; import { EuiSwitch, @@ -20,73 +18,60 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiLink, - EuiToolTip, - EuiSpacer } from '@elastic/eui'; -import { WzPermissionsFormatted } from './format'; +import { IWzElementPermissionsProps, WzElementPermissions } from './element'; -export interface IUserPermissionsObject{action: string, resource: string}; -export type TUserPermissionsFunction = (props : any) => TUserPermissions; -export type TUserPermissions = (string | IUserPermissionsObject)[] | null; -export type TUserRoles = string[] | null; -export type TUserRolesFunction = (props : any) => TUserRoles; +interface IWzButtonPermissionsProps + extends Omit< + IWzElementPermissionsProps, + 'children' | 'additionalPropsFunction' + > { + buttonType?: 'default' | 'empty' | 'icon' | 'link' | 'switch'; + rest: any; +} -interface IWzButtonPermissionsProps{ - permissions?: TUserPermissions | TUserPermissionsFunction - roles?: TUserRoles | TUserRolesFunction - buttonType?: 'default' | 'empty' | 'icon' | 'link' | 'switch' - tooltip?: any - rest?: any -}; - -export const WzButtonPermissions = ({permissions = null, roles = null, buttonType = 'default', tooltip, ...rest} : IWzButtonPermissionsProps) => { - const [userPermissionRequirements, userPermissions] = useUserPermissionsRequirements(typeof permissions === 'function' ? permissions(rest) : permissions); - const [userRolesRequirements, userRoles] = useUserRolesRequirements(typeof roles === 'function' ? roles(rest) : roles); +export const WzButtonPermissions = ({ + buttonType = 'default', + permissions, + roles, + tooltip, + ...rest +}: IWzButtonPermissionsProps) => { + const Button = + buttonType === 'empty' + ? EuiButtonEmpty + : buttonType === 'icon' + ? EuiButtonIcon + : buttonType === 'link' + ? EuiLink + : buttonType === 'switch' + ? EuiSwitch + : EuiButton; - const Button = buttonType === 'default' ? EuiButton - : buttonType === 'empty' ? EuiButtonEmpty - : buttonType === 'icon' ? EuiButtonIcon - : buttonType === 'link' ? EuiLink - : buttonType === 'switch' ? EuiSwitch - : null - const disabled = Boolean(userRolesRequirements || userPermissionRequirements || rest.isDisabled || rest.disabled); - const disabledProp = !['link', 'switch'].includes(buttonType) ? { isDisabled: disabled } : { disabled }; - const onClick = disabled || !rest.onClick ? undefined : rest.onClick; - const onChange = disabled || !rest.onChange ? undefined : rest.onChange; - const customProps = { ...rest, onChange, onClick }; + return ( + { + const additionalProps = { + ...(!['link', 'switch'].includes(buttonType) + ? { isDisabled: disabled } + : { disabled }), + onClick: + disabled || !rest.onClick || buttonType == 'switch' + ? undefined + : rest.onClick, + onChange: disabled || !rest.onChange ? undefined : rest.onChange, + }; - if (buttonType == 'switch') delete customProps.onClick; + if (buttonType == 'switch') delete additionalProps.onClick; - const button = @@ -2582,7 +2598,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -2611,7 +2631,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -2720,7 +2744,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -2934,7 +2962,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -2963,7 +2995,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3072,7 +3108,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3126,7 +3166,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3303,7 +3347,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3332,7 +3380,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3441,7 +3493,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3495,7 +3551,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3675,7 +3735,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3704,7 +3768,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3813,7 +3881,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -3868,7 +3940,11 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + 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/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" > - - + > + + + - - + > + + + - - + > + + + - - + > + + + - - + > + + + + > + + @@ -80,7 +84,11 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -109,7 +117,11 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -143,7 +155,11 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -249,7 +265,11 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -303,7 +323,11 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + diff --git a/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap b/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap index 436f919ef7..73cd80d050 100644 --- a/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap +++ b/plugins/main/public/components/health-check/components/__snapshots__/check-result.test.tsx.snap @@ -54,7 +54,7 @@ exports[`Check result component should render a Check result screen 1`] = ` onFocus={[Function]} type="clock" > - - + > + + + diff --git a/plugins/main/public/components/overview/mitre_attack_intelligence/__snapshots__/intelligence.test.tsx.snap b/plugins/main/public/components/overview/mitre_attack_intelligence/__snapshots__/intelligence.test.tsx.snap index adf70d0cc4..2fe8a49688 100644 --- a/plugins/main/public/components/overview/mitre_attack_intelligence/__snapshots__/intelligence.test.tsx.snap +++ b/plugins/main/public/components/overview/mitre_attack_intelligence/__snapshots__/intelligence.test.tsx.snap @@ -13,7 +13,11 @@ exports[`Module Mitre Att&ck intelligence container should render permissions pr viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
@@ -104,7 +108,11 @@ exports[`Module Mitre Att&ck intelligence container should render the component viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
@@ -260,7 +268,11 @@ exports[`Module Mitre Att&ck intelligence container should render the component viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + @@ -332,7 +344,11 @@ exports[`Module Mitre Att&ck intelligence container should render the component viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + diff --git a/plugins/main/public/components/search-bar/query-language/__snapshots__/aql.test.tsx.snap b/plugins/main/public/components/search-bar/query-language/__snapshots__/aql.test.tsx.snap index 0ef68d2e9e..3f9bd54fc2 100644 --- a/plugins/main/public/components/search-bar/query-language/__snapshots__/aql.test.tsx.snap +++ b/plugins/main/public/components/search-bar/query-language/__snapshots__/aql.test.tsx.snap @@ -39,7 +39,11 @@ exports[`SearchBar component Renders correctly to match the snapshot of query la viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + diff --git a/plugins/main/public/components/settings/about/__snapshots__/generalInfo.test.tsx.snap b/plugins/main/public/components/settings/about/__snapshots__/generalInfo.test.tsx.snap index 50897d09a9..c47b5eefec 100644 --- a/plugins/main/public/components/settings/about/__snapshots__/generalInfo.test.tsx.snap +++ b/plugins/main/public/components/settings/about/__snapshots__/generalInfo.test.tsx.snap @@ -76,7 +76,11 @@ exports[`SettingsAboutGeneralInfo component should render a component 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
+ > + +
diff --git a/plugins/main/public/components/settings/api/available-updates-flyout/__snapshots__/update-detail.test.tsx.snap b/plugins/main/public/components/settings/api/available-updates-flyout/__snapshots__/update-detail.test.tsx.snap index 08a8b8e821..79276268d1 100644 --- a/plugins/main/public/components/settings/api/available-updates-flyout/__snapshots__/update-detail.test.tsx.snap +++ b/plugins/main/public/components/settings/api/available-updates-flyout/__snapshots__/update-detail.test.tsx.snap @@ -27,7 +27,11 @@ exports[`UpdateDetail component should return the UpdateDetail component 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + +
+ > + + @@ -147,7 +155,11 @@ exports[`UpdateDetail component should return the UpdateDetail component 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + diff --git a/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap b/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap index e0d47aa5f6..6af540478d 100644 --- a/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap +++ b/plugins/main/public/controllers/management/components/management/configuration/office365/components/SubscriptionTab/__snapshots__/SubscriptionTab.test.tsx.snap @@ -152,7 +152,7 @@ exports[`SubscriptionTab component renders correctly to match the snapshot 1`] = size="m" type="questionInCircle" > - - + > + + + - - + > + + + - - + > + + + li { position: relative; } + > li { + position: relative; + } } .#{$fa-css-prefix}-li { position: absolute; left: -$fa-li-width; width: $fa-li-width; - top: (2em / 14); + top: calc(2em / 14); text-align: center; &.#{$fa-css-prefix}-lg { - left: -$fa-li-width + (4em / 14); + left: -$fa-li-width + calc(4em / 14); } } diff --git a/plugins/main/public/utils/fontawesome/scss/_variables.scss b/plugins/main/public/utils/fontawesome/scss/_variables.scss index a5a89ef97b..a5a4420d1b 100644 --- a/plugins/main/public/utils/fontawesome/scss/_variables.scss +++ b/plugins/main/public/utils/fontawesome/scss/_variables.scss @@ -9,7 +9,7 @@ $fa-css-prefix: fa !default; $fa-version: "4.6.3" !default; $fa-border-color: #eee !default; $fa-inverse: #fff !default; -$fa-li-width: (30em / 14) !default; +$fa-li-width: calc(30em / 14) !default; $fa-var-500px: "\f26e"; $fa-var-adjust: "\f042"; diff --git a/plugins/main/server/routes/wazuh-reporting.test.ts b/plugins/main/server/routes/wazuh-reporting.test.ts index 075f411d7b..231eaf0382 100644 --- a/plugins/main/server/routes/wazuh-reporting.test.ts +++ b/plugins/main/server/routes/wazuh-reporting.test.ts @@ -214,11 +214,11 @@ describe('[endpoint] PUT /utils/configuration', () => { // If any of the parameters is changed this variable should be updated with the new md5 it.each` footer | header | responseStatusCode | expectedMD5 | tab - ${null} | ${null} | ${200} | ${'301281824427c6ea8546fd14ee1aa5d8'} | ${'pm'} - ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'} | ${200} | ${'c2adfd7ab05ae3ed1548abd3c8be8f7e'} | ${'general'} - ${''} | ${''} | ${200} | ${'06726f42a4129dd47262ea7228939006'} | ${'fim'} - ${'Custom Footer'} | ${null} | ${200} | ${'1ea187181c307a4be5e90a38f614c42d'} | ${'aws'} - ${null} | ${'Custom Header'} | ${200} | ${'f2fc0804eb52ebca21291eb5a40dec35'} | ${'gcp'} + ${null} | ${null} | ${200} | ${'7f497384a622d116b260e14c7bd9d0dc'} | ${'pm'} + ${'Custom\nFooter'} | ${'info@company.com\nFake Avenue 123'} | ${200} | ${'db832dc7cb2eb918d5e2df1f6cecb8b1'} | ${'general'} + ${''} | ${''} | ${200} | ${'cb39c81684c5a9b19cbf5a38dc19061c'} | ${'fim'} + ${'Custom Footer'} | ${null} | ${200} | ${'11603a29c2b90979161c6e1b09cfe345'} | ${'aws'} + ${null} | ${'Custom Header'} | ${200} | ${'67d868e5655a1a7068f457348a8a35c8'} | ${'gcp'} `( `Set custom report header and footer - Verify PDF output`, async ({ footer, header, responseStatusCode, expectedMD5, tab }) => { @@ -257,7 +257,6 @@ describe('[endpoint] PUT /utils/configuration', () => { .put('/utils/configuration') .send(configurationBody) .expect(responseStatusCode); - return; if (typeof footer == 'string') { expect( responseConfig.body?.data?.updatedConfiguration?.[ diff --git a/plugins/wazuh-check-updates/package.json b/plugins/wazuh-check-updates/package.json index 4804025d53..0c24d9e311 100644 --- a/plugins/wazuh-check-updates/package.json +++ b/plugins/wazuh-check-updates/package.json @@ -3,7 +3,7 @@ "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.11.0" + "version": "2.12.0" }, "description": "Wazuh Check Updates", "private": true, diff --git a/plugins/wazuh-check-updates/public/components/__snapshots__/dismiss-notification-check.test.tsx.snap b/plugins/wazuh-check-updates/public/components/__snapshots__/dismiss-notification-check.test.tsx.snap index 03d28de0a9..6d22dc8887 100644 --- a/plugins/wazuh-check-updates/public/components/__snapshots__/dismiss-notification-check.test.tsx.snap +++ b/plugins/wazuh-check-updates/public/components/__snapshots__/dismiss-notification-check.test.tsx.snap @@ -32,7 +32,11 @@ exports[`DismissNotificationCheck component should render the check 1`] = ` viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" - /> + > + + diff --git a/plugins/wazuh-core/package.json b/plugins/wazuh-core/package.json index 1317a3b47c..5ed3e6c72f 100644 --- a/plugins/wazuh-core/package.json +++ b/plugins/wazuh-core/package.json @@ -3,7 +3,7 @@ "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.11.0" + "version": "2.12.0" }, "description": "Wazuh Core", "private": true, diff --git a/plugins/wazuh-endpoints/package.json b/plugins/wazuh-endpoints/package.json index e79948329b..6fb24b72b5 100644 --- a/plugins/wazuh-endpoints/package.json +++ b/plugins/wazuh-endpoints/package.json @@ -3,7 +3,7 @@ "version": "4.9.0", "revision": "00", "pluginPlatform": { - "version": "2.11.0" + "version": "2.12.0" }, "description": "Wazuh Endpoints", "private": true, From 1bc5bb64c33022abe389132093e70d48ce9838c2 Mon Sep 17 00:00:00 2001 From: Luciano Gorza <103193307+lucianogorza@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:08:51 -0300 Subject: [PATCH 030/136] Select agents and add/remove groups (#6274) * Add global action to agents table * Add global action remove groups from agents * Add warning to remove groups from agents * Add results to modal * Improve global actions to agent table * Improve modal result * Manage API response * Improve result modal * Fix table layout * Clear table selection on refresh * Fix filters and clear selection on sort and pagination * Fix filters when user type * Improve parameters in service funtions * Paginate getAgents and getGroups * Paginate add and remove agents to groups * Update CHANGELOG * Update test snapshots * Fix pageSize on add-remove agents * Fix snapshot * Fix snapshot checkbox id and label * Fix snapshot checkbox id and label in Mobile * Add unit test for services * Add unit test for hooks * Add unit test for React components * Update snapshots * Remove comments --- CHANGELOG.md | 2 + .../__snapshots__/inventory.test.tsx.snap | 9950 +++++++++-------- .../__snapshots__/table-wz-api.test.tsx.snap | 1153 +- .../common/tables/table-with-search-bar.tsx | 6 + .../components/common/tables/table-wz-api.tsx | 112 +- .../endpoints-summary/hooks/agents.test.ts | 38 + .../endpoints-summary/hooks/agents.ts | 11 +- .../endpoints-summary/hooks/groups.test.ts | 44 + .../endpoints-summary/hooks/groups.ts | 2 +- .../endpoints-summary/hooks/index.ts | 1 + .../components/endpoints-summary/index.tsx | 2 +- .../services/add-agent-to-group.tsx | 13 +- .../services/add-agents-to-group.tsx | 9 + .../services/get-agents.test.tsx | 85 + .../endpoints-summary/services/get-agents.tsx | 54 + .../services/get-groups.test.tsx | 78 + .../endpoints-summary/services/get-groups.tsx | 56 +- .../services/get-total-agents.tsx | 12 - .../endpoints-summary/services/index.tsx | 5 +- .../services/paginated-agents-group.test.tsx | 205 + .../services/paginated-agents-group.tsx | 109 + .../services/remove-agent-from-group.tsx | 6 - .../services/remove-agent-from-groups.tsx | 14 +- .../services/remove-agents-from-group.tsx | 9 + .../__snapshots__/agents-table.test.tsx.snap | 2676 +++-- .../edit-groups-modal.test.tsx.snap | 15 + .../table/{ => actions}/actions.tsx | 16 +- .../table/actions/edit-groups-modal.test.tsx | 118 + .../table/{ => actions}/edit-groups-modal.tsx | 52 +- .../table/agents-table.test.tsx | 39 +- .../endpoints-summary/table/agents-table.tsx | 357 +- .../endpoints-summary/table/columns.tsx | 178 + .../global-actions.test.tsx.snap | 43 + .../edit-groups-modal.test.tsx.snap | 8 + .../edit-groups/edit-groups-modal.test.tsx | 100 + .../edit-groups/edit-groups-modal.tsx | 314 + .../global-actions/edit-groups/result.tsx | 264 + .../global-actions/global-actions.test.tsx | 56 + .../table/global-actions/global-actions.tsx | 131 + .../components/endpoints-summary/types.ts | 32 + .../__snapshots__/intelligence.test.tsx.snap | 402 +- .../main/public/react-services/wz-request.ts | 25 +- 42 files changed, 9735 insertions(+), 7067 deletions(-) create mode 100644 plugins/main/public/components/endpoints-summary/hooks/agents.test.ts create mode 100644 plugins/main/public/components/endpoints-summary/hooks/groups.test.ts create mode 100644 plugins/main/public/components/endpoints-summary/services/add-agents-to-group.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/get-agents.test.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/get-agents.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/get-groups.test.tsx delete mode 100644 plugins/main/public/components/endpoints-summary/services/get-total-agents.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/paginated-agents-group.test.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/paginated-agents-group.tsx delete mode 100644 plugins/main/public/components/endpoints-summary/services/remove-agent-from-group.tsx create mode 100644 plugins/main/public/components/endpoints-summary/services/remove-agents-from-group.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/actions/__snapshots__/edit-groups-modal.test.tsx.snap rename plugins/main/public/components/endpoints-summary/table/{ => actions}/actions.tsx (82%) create mode 100644 plugins/main/public/components/endpoints-summary/table/actions/edit-groups-modal.test.tsx rename plugins/main/public/components/endpoints-summary/table/{ => actions}/edit-groups-modal.tsx (73%) create mode 100644 plugins/main/public/components/endpoints-summary/table/columns.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/global-actions/__snapshots__/global-actions.test.tsx.snap create mode 100644 plugins/main/public/components/endpoints-summary/table/global-actions/edit-groups/__snapshots__/edit-groups-modal.test.tsx.snap create mode 100644 plugins/main/public/components/endpoints-summary/table/global-actions/edit-groups/edit-groups-modal.test.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/global-actions/edit-groups/edit-groups-modal.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/global-actions/edit-groups/result.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/global-actions/global-actions.test.tsx create mode 100644 plugins/main/public/components/endpoints-summary/table/global-actions/global-actions.tsx create mode 100644 plugins/main/public/components/endpoints-summary/types.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 324f3316ba..d17237185e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 4.9.0 - Added AngularJS dependencies [#6145](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6145) +- 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) ###Ā Changed diff --git a/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap b/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap index 37bcbeb939..665925c7d8 100644 --- a/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap +++ b/plugins/main/public/components/agents/syscollector/__snapshots__/inventory.test.tsx.snap @@ -129,185 +129,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" >
-
-

- Network interfaces - - (0) - -

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

+ Network ports + + (0) + +

+
+
+ +
+
+
-
- - +
-
+ + + + +
+
+
+
+
- -
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
- -
- + + - -
-
- +
- No items found - - - -
+ + + Local port + + + + + + + Local IP address + + + + + + + + + + + + + + +
+ + No items found + +
+ + + + +
+
+
+
-
-

- Network ports - - (0) - -

-
- + Network settings + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + +
+
+ + - - - - - - - - - - - - - + - - - - - - -
-
- - - Local port - - - - - - Local IP address - - - - - - State - - - - - + - - Protocol - - - -
-
- + + Protocol + + + + +
- No items found - - - -
+ +
+
+ + No items found + +
+
+
+
@@ -838,192 +1285,268 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = ` class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive" >
-

- Network settings - - (0) - -

-
-
-
- + Packages + + (0) + + +
+
- +
+
+ + + + + Export formatted + + + +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - - + - + - + + + + + - - - - - - -
-
- - - - - Address - - - - - + - - Netmask - - - - - + - - Protocol - - - - - +
+
- Broadcast - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -1231,244 +1699,53 @@ 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) - -

-
- + Processes + + (0) + + +
+
- -
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - -
-
- - - - - - +
-
- -
-
- - No items found - -
-
-
-
-
-
-
-
-
-
-
-
-

- Processes - - (0) - -

-
-
-
-
- -
-
- +
+
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + +
+
+ + - - - - - - - - - - - + - - + - + - + - + + + + + - - - - - - -
-
- - - Name - - - - - - + - - Effective user - - - - - - - + - - Parent PID - - - - - + - - VM size - - - - - + - - Priority - - - - - +
+
- State - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -2177,185 +2277,261 @@ 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 + + + +
+
+
@@ -2559,607 +2679,437 @@ 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 + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + +
+
- - - - - - - - - - - + - + - + - + + + + + - - - - - - -
-
- - - Name - - - - - - + - - Architecture - - - - - + - - Version - - - - - + - - Vendor - - - - - +
+
- Description - - - - -
-
- - No items found - -
-
+ + No items found + + + +
+
+
@@ -3696,185 +3468,261 @@ 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 - - - - + + Description + + + +
+
+ + No items found + +
+
+ + + + + + + +
+
+
+
+
+
+
+
+
- -
+ + + +
+
+
-
+
-
+ + + + +
+
+
+
+
- -
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
- -
- + + + + + + + + + + - + - + - + + + + + + + + - - - - - - -
+
- - Size - - - - - + - - Session - - - - - + - - Priority - - - - - + + + + + + + + + + + + + + + - - State - - - -
-
- + + Priority + + + + +
- No items found - - - -
+ +
+
+ + No items found + +
+
+
+
@@ -4375,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 + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + +
+
+ + - - - - - - - - - - - - - + - + - - - - - - - -
-
- - - - - - - State - - - - - + - - MTU - - - - - + - - Type - - - -
-
- - No items found - -
-
+ + + Type + + + +
+
+ + No items found + +
+
+
+
@@ -4758,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 + +
+ + + + +
+
@@ -5115,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 + + + +
+
+
@@ -5498,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 + + + +
+
-
-
-
-
-
-
-
+ class="euiSpacer euiSpacer--s" + /> +
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + +
+
- - - - +
+
+ + No items found + +
+
- - - - - - - - - - - -
-
- -
-
- - No items found - -
-
@@ -5788,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 + + + +
+
+
@@ -6152,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/tables/__snapshots__/table-wz-api.test.tsx.snap b/plugins/main/public/components/common/tables/__snapshots__/table-wz-api.test.tsx.snap index 968ff7dfc8..2655f8c2ba 100644 --- a/plugins/main/public/components/common/tables/__snapshots__/table-wz-api.test.tsx.snap +++ b/plugins/main/public/components/common/tables/__snapshots__/table-wz-api.test.tsx.snap @@ -36,620 +36,661 @@ exports[`Table WZ API component renders correctly to match the snapshot 1`] = ` title="Table" >
- -
- -

- 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/table-with-search-bar.tsx b/plugins/main/public/components/common/tables/table-with-search-bar.tsx index 1f60167d3a..2c551192aa 100644 --- a/plugins/main/public/components/common/tables/table-with-search-bar.tsx +++ b/plugins/main/public/components/common/tables/table-with-search-bar.tsx @@ -124,6 +124,7 @@ export function TableWithSearchBar({ 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..43b83e72de 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,20 @@ export function TableWzAPI({ showReload?: boolean; searchBarProps?: any; reload?: boolean; + onDataChange?: Function; }) { 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 +117,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,19 +142,23 @@ 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 @@ -167,28 +181,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 +286,15 @@ export function TableWzAPI({ ); return ( - <> - {header} - {rest.description && } - {table} - + + {header} + {rest.description && ( + + {rest.description} + + )} + {table} + ); } 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/services/add-agent-to-group.tsx b/plugins/main/public/components/endpoints-summary/services/add-agent-to-group.tsx index d32a186f14..eee4e590bb 100644 --- a/plugins/main/public/components/endpoints-summary/services/add-agent-to-group.tsx +++ b/plugins/main/public/components/endpoints-summary/services/add-agent-to-group.tsx @@ -1,4 +1,13 @@ +import IApiResponse from '../../../react-services/interfaces/api-response.interface'; import { WzRequest } from '../../../react-services/wz-request'; -export const addAgentToGroupService = async (agentId: string, group: string) => - 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.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-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-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 ca93426bbd..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,143 +12,54 @@ exports[`AgentsTable component Renders correctly to match the snapshot 1`] = ` class="euiFlexItem" >
-
-

- Agents - - (0) - -

-
-
- -
-
- + Agents + + (0) + + +
+
- - - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
+ Deploy new agent + + +
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - + + + + + + + + + + + + + + +
-
- - - +
-
+
- -
+ + + + + + +
+
+
+
+
- -
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
- -
- - - + + + + + + + + + + - + - - - - - - -
+
- - Synced - - - - - - + +
+
+ +
- Actions - - -
-
- + + + ID + + + + + +
- No items found - - - -
+ +
+ + + + + + + + + + + + + + + Actions + + +
+
+ + No items found + +
+
+
+
@@ -558,250 +664,86 @@ exports[`AgentsTable component Renders correctly to match the snapshot with cust class="euiFlexItem" >
-
-

- Agents - - (0) - -

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

- Agents - - (0) - -

-
-
- + Agents + + (0) + + +
+
- -
-
- - - -
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
+ Deploy new agent + + +
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - + +
-
- - - +
-
+
- -
+ + + + + + +
+
+
+
+
- -
+ +
+
+ +
+
+ + + + + + +
+
+
+
+
- -
- - - + + + + + + + + + + + + + + + + + + + - + + + - - Actions - - - - - - - - - - -
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + - Status + + Actions + - - - - +
-
- - No items found - -
-
+
+ + 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`] = ` +