From 190d2e4418958a20f8ba9a5be55c77737d4a6aa3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Sep 2022 17:58:10 +0200 Subject: [PATCH] [Backport 4.4-1.2-wzd] [PR] Add agent synchronization statistics (#4442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [PR] Add agent synchronization statistics (#3874) * Added Synced stats and pending agents * Added mocked column synced * Changed pending position and color * add agent-synced.tsx * change date:data * Update GET /agents/summary/status response handler * add changelog * Deprecated is_sync endpoint * Created synced constants and updated endpoint definitions Co-authored-by: Ian Yenien Serrano <63758389+yenienserrano@users.noreply.github.com> Co-authored-by: yenienserrano Co-authored-by: Álex (cherry picked from commit 44570272566d78fb5ae772b0f9a3061af66171aa) Co-authored-by: Federico Rodriguez --- CHANGELOG.md | 2 + common/api-info/endpoints.json | 51 +++++++++++++++---- common/constants.ts | 9 +++- public/components/agents/agent-synced.tsx | 22 ++++++++ .../tools/devtools/api-requests-list.js | 7 --- public/controllers/agent/agents-preview.js | 4 +- public/controllers/agent/agents.js | 21 ++------ .../agent/components/agents-preview.js | 15 +++++- .../agent/components/agents-table.js | 50 ++++++++++-------- .../configuration/utils/wz-fetch.js | 5 +- .../management/status/actions-buttons-main.js | 2 +- .../management/status/status-overview.js | 3 +- .../management/status/status-stats.js | 18 ++++++- public/controllers/overview/overview.js | 2 +- 14 files changed, 143 insertions(+), 68 deletions(-) create mode 100644 public/components/agents/agent-synced.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 4616d9264f..a0b3a35699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ All notable changes to the Wazuh app project will be documented in this file. ### Added - Added the option to sort by the agents count in the group table. [#4323](https://github.com/wazuh/wazuh-kibana-app/pull/4323) +- Added agent synchronization status in the agent module. [#3874](https://github.com/wazuh/wazuh-kibana-app/pull/3874) ### Changed - Changed the HTTP verb from `GET` to `POST` in the requests to login to the Wazuh API [#4103](https://github.com/wazuh/wazuh-kibana-app/pull/4103) +- Endpoint `/agents/summary/status` response was adapted. [#3874](https://github.com/wazuh/wazuh-kibana-app/pull/3874) ## Wazuh v4.3.7 - OpenSearch Dashboards 1.2.0 - Revision 4308 diff --git a/common/api-info/endpoints.json b/common/api-info/endpoints.json index b1e8e12109..74175e672d 100644 --- a/common/api-info/endpoints.json +++ b/common/api-info/endpoints.json @@ -52,6 +52,17 @@ "format": "group_names" } }, + { + "name": "group_config_status", + "description": "Agent groups configuration sync status", + "schema": { + "type": "string", + "enum": [ + "synced", + "not synced" + ] + } + }, { "name": "ip", "description": "Filter by the IP used by the agent to communicate with the manager. If it's not available, it will have the same value as registerIP", @@ -206,7 +217,7 @@ }, { "name": "version", - "description": "Filter by agents version", + "description": "Filter by agents version using one of the following formats: 'X.Y.Z', 'vX.Y.Z', 'wazuh X.Y.Z' or 'wazuh vX.Y.Z'. For example: '4.4.0'", "schema": { "type": "string", "format": "alphanumeric" @@ -852,7 +863,7 @@ }, { "name": "version", - "description": "Filter by agents version", + "description": "Filter by agents version using one of the following formats: 'X.Y.Z', 'vX.Y.Z', 'wazuh X.Y.Z' or 'wazuh vX.Y.Z'. For example: '4.4.0'", "schema": { "type": "string", "format": "alphanumeric" @@ -1105,7 +1116,6 @@ "socket", "syscheck", "syslog_output", - "agent-key-polling", "aws-s3", "azure-logs", "cis-cat", @@ -3226,7 +3236,7 @@ }, { "name": "version", - "description": "Filter by agents version", + "description": "Filter by agents version using one of the following formats: 'X.Y.Z', 'vX.Y.Z', 'wazuh X.Y.Z' or 'wazuh vX.Y.Z'. For example: '4.4.0'", "schema": { "type": "string", "format": "alphanumeric" @@ -4525,7 +4535,6 @@ "socket", "syscheck", "syslog_output", - "agent-key-polling", "aws-s3", "azure-logs", "cis-cat", @@ -6904,6 +6913,25 @@ } ] }, + { + "name": "/security/user/authenticate", + "documentation": "https://documentation.wazuh.com/current/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" + ], + "query": [ + { + "name": "raw", + "description": "Format response in plain text", + "required": false, + "schema": { + "type": "boolean" + } + } + ] + }, { "name": "/security/users", "documentation": "https://documentation.wazuh.com/4.3/user-manual/api/reference.html#operation/api.controllers.security_controller.get_users", @@ -9394,7 +9422,7 @@ }, { "name": "version", - "description": "Filter by agents version", + "description": "Filter by agents version using one of the following formats: 'X.Y.Z', 'vX.Y.Z', 'wazuh X.Y.Z' or 'wazuh vX.Y.Z'. For example: '4.4.0'", "schema": { "type": "string", "format": "alphanumeric" @@ -9548,7 +9576,7 @@ }, { "name": "version", - "description": "Filter by agents version", + "description": "Filter by agents version using one of the following formats: 'X.Y.Z', 'vX.Y.Z', 'wazuh X.Y.Z' or 'wazuh vX.Y.Z'. For example: '4.4.0'", "schema": { "type": "string", "format": "alphanumeric" @@ -10549,9 +10577,10 @@ "body": [ { "name": "group_id", - "description": "Group name", + "description": "Group name. It can contain any of the characters between a-z, A-Z, 0-9, '_', '-' and '.'. Names '.' and '..' are restricted.", "type": "string", - "format": "group_names" + "format": "group_names", + "maxLength": 128 } ] }, @@ -11132,7 +11161,7 @@ }, { "name": "version", - "description": "Filter by agents version", + "description": "Filter by agents version using one of the following formats: 'X.Y.Z', 'vX.Y.Z', 'wazuh X.Y.Z' or 'wazuh vX.Y.Z'. For example: '4.4.0'", "schema": { "type": "string", "format": "alphanumeric" @@ -12010,4 +12039,4 @@ } ] } -] +] \ No newline at end of file diff --git a/common/constants.ts b/common/constants.ts index 23fe303c22..fb4fb98d2d 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -401,8 +401,13 @@ export const UI_ORDER_AGENT_STATUS = [ API_NAME_AGENT_STATUS.ACTIVE, API_NAME_AGENT_STATUS.DISCONNECTED, API_NAME_AGENT_STATUS.PENDING, - API_NAME_AGENT_STATUS.NEVER_CONNECTED -]; + API_NAME_AGENT_STATUS.NEVER_CONNECTED +] + +export const AGENT_SYNCED_STATUS = { + SYNCED: 'synced', + NOT_SYNCED: 'not synced', +} // Documentation export const DOCUMENTATION_WEB_BASE_URL = "https://documentation.wazuh.com"; diff --git a/public/components/agents/agent-synced.tsx b/public/components/agents/agent-synced.tsx new file mode 100644 index 0000000000..a5ac3f55cc --- /dev/null +++ b/public/components/agents/agent-synced.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { EuiHealth } from "@elastic/eui"; +import { AGENT_SYNCED_STATUS } from '../../../common/constants'; + +interface SyncedProps { + synced: string; +} + +export const AgentSynced = ({ synced }: SyncedProps) => { + const color = { + [AGENT_SYNCED_STATUS.SYNCED]: 'success', + [AGENT_SYNCED_STATUS.NOT_SYNCED]: 'subdued', + }[synced]; + + return ( + + + {synced} + + + ); +} \ No newline at end of file diff --git a/public/components/tools/devtools/api-requests-list.js b/public/components/tools/devtools/api-requests-list.js index 3054dd78cf..739c18870f 100644 --- a/public/components/tools/devtools/api-requests-list.js +++ b/public/components/tools/devtools/api-requests-list.js @@ -221,13 +221,6 @@ export const apiRequestList = "meta": "endpoint", "documentationLink": "https://documentation.wazuh.com/current/user-manual/api/reference.html#get-active-configuration" }, - { - "name": "agents/:agent_id/group/is_sync", - "value": "agents/:agent_id/group/is_sync", - "score": 1, - "meta": "endpoint", - "documentationLink": "https://documentation.wazuh.com/current/user-manual/api/reference.html#get-sync-status-of-agent" - }, { "name": "agents/:agent_id/key", "value": "agents/:agent_id/key", diff --git a/public/controllers/agent/agents-preview.js b/public/controllers/agent/agents-preview.js index 401c8c6833..be9da8261e 100644 --- a/public/controllers/agent/agents-preview.js +++ b/public/controllers/agent/agents-preview.js @@ -77,8 +77,8 @@ export class AgentsPreviewController { if (loc && loc.tab) { this.submenuNavItem = loc.tab; } - const summaryData = await WzRequest.apiReq('GET', '/agents/summary/status', {}); - this.summary = summaryData.data.data; + const {data: {data: summaryData}} = await WzRequest.apiReq('GET', '/agents/summary/status', {}); + this.summary = summaryData.connection; if (this.summary.total === 0) { if (this.addingNewAgent === undefined) { this.addNewAgent(true); diff --git a/public/controllers/agent/agents.js b/public/controllers/agent/agents.js index b5ae910752..f557807b2c 100644 --- a/public/controllers/agent/agents.js +++ b/public/controllers/agent/agents.js @@ -306,8 +306,6 @@ export class AgentsController { }; this.$scope.switchConfigurationTab = (configurationTab, navigate) => { - // Check if configuration is synced - this.checkSync(); this.$scope.navigate = navigate; this.configurationHandler.switchConfigurationTab(configurationTab, this.$scope); if (!this.$scope.navigate) { @@ -510,8 +508,11 @@ export class AgentsController { }, }); this.$scope.agent.status = - (((((agentInfo || {}).data || {}).data || {}).affected_items || [])[0] || {}).status || + agentInfo?.data?.data?.affected_items?.[0]?.status || this.$scope.agent.status; + this.$scope.isSynchronized = this.$scope.agent.status?.synced; + + this.$scope.$applyAsync(); } catch (error) { throw new Error(error); } @@ -738,20 +739,6 @@ export class AgentsController { this.$scope.agent.syscheck = result; } - /** - * Checks if configuration is sync - */ - async checkSync() { - const isSync = await WzRequest.apiReq( - 'GET', - `/agents/${this.$scope.agent.id}/group/is_sync`, - {} - ); - this.$scope.isSynchronized = - (((((isSync || {}).data || {}).data || {}).affected_items || [])[0] || {}).synced || false; - this.$scope.$applyAsync(); - } - /** * Get the needed data for load syscollector * @param {*} id diff --git a/public/controllers/agent/components/agents-preview.js b/public/controllers/agent/components/agents-preview.js index 735126e2d7..d7251b02e5 100644 --- a/public/controllers/agent/components/agents-preview.js +++ b/public/controllers/agent/components/agents-preview.js @@ -66,7 +66,8 @@ export const AgentsPreview = compose( loading: false, showAgentsEvolutionVisualization: false, agentTableFilters: [], - agentStatusSummary: {} + agentStatusSummary: {}, + agentConfiguration: {}, }; this.wazuhConfig = new WazuhConfig(); this.agentStatus = UI_ORDER_AGENT_STATUS.map(agentStatus => ({ @@ -110,7 +111,7 @@ export const AgentsPreview = compose( async fetchAgentStatusDetailsData(){ try { this.setState({ loading: true }); - const {data: {data: agentStatusSummary}} = await WzRequest.apiReq('GET', '/agents/summary/status', {}); + const {data: {data: { connection: agentStatusSummary, configuration: agentConfiguration }}} = await WzRequest.apiReq('GET', '/agents/summary/status', {}); const {data: {data: {affected_items: [lastRegisteredAgent]}}} = await WzRequest.apiReq('GET', '/agents', { params: { limit: 1, sort: '-dateAdd', q: 'id!=000' }, @@ -121,7 +122,9 @@ export const AgentsPreview = compose( loading: false, lastRegisteredAgent, agentStatusSummary, + agentConfiguration, agentsActiveCoverage: ((agentStatusSummary.active/agentStatusSummary.total)*100).toFixed(2), + agentsSynced: ((agentConfiguration.synced/agentConfiguration.total)*100).toFixed(2), agentMostActive }); } catch (error) { @@ -216,6 +219,14 @@ export const AgentsPreview = compose( className="white-space-nowrap" /> + + + {this.state.lastRegisteredAgent && ( diff --git a/public/controllers/agent/components/agents-table.js b/public/controllers/agent/components/agents-table.js index 82d6c5e512..cd249cab5e 100644 --- a/public/controllers/agent/components/agents-table.js +++ b/public/controllers/agent/components/agents-table.js @@ -43,10 +43,11 @@ import { getAgentFilterValues } from '../../../controllers/management/components import { WzButtonPermissions } from '../../../components/common/permissions/button'; import { formatUIDate } from '../../../react-services/time-service'; import { withErrorBoundary } from '../../../components/common/hocs'; -import { API_NAME_AGENT_STATUS, UI_LOGGER_LEVELS, UI_ORDER_AGENT_STATUS } from '../../../../common/constants'; +import { API_NAME_AGENT_STATUS, UI_LOGGER_LEVELS, UI_ORDER_AGENT_STATUS, AGENT_SYNCED_STATUS } from '../../../../common/constants'; import { UI_ERROR_SEVERITIES } from '../../../react-services/error-orchestrator/types'; import { getErrorOrchestrator } from '../../../react-services/common-services'; import { AgentStatus } from '../../../components/agents/agent_status'; +import { AgentSynced } from '../../../components/agents/agent-synced'; export const AgentsTable = withErrorBoundary( class AgentsTable extends Component { @@ -77,6 +78,13 @@ export const AgentsTable = withErrorBoundary( operators: ['=', '!='], values: UI_ORDER_AGENT_STATUS, }, + { + type: 'q', + label: 'group_config_status', + description: 'Filter by agent synced configuration status', + operators: ['=', '!='], + values: [AGENT_SYNCED_STATUS.SYNCED, AGENT_SYNCED_STATUS.NOT_SYNCED], + }, { type: 'q', label: 'os.platform', @@ -168,15 +176,15 @@ export const AgentsTable = withErrorBoundary( async UNSAFE_componentWillMount() { const managerVersion = await WzRequest.apiReq('GET', '/', {}); const totalAgent = await WzRequest.apiReq('GET', '/agents', {}); - const agentActive = await WzRequest.apiReq('GET', '/agents/summary/status', {}); + const {data: {data: {connection: agentActive}}} = await WzRequest.apiReq('GET', '/agents/summary/status', {}); + - this.setState({ managerVersion: managerVersion.data.data.api_version, avaibleAgents: totalAgent.data.data.affected_items, - agentActive: agentActive.data.data.active + agentActive.data.data.disconnected, + agentActive: agentActive.active + agentActive.disconnected, }); - } + } onTableChange = ({ page = {}, sort = {} }) => { const { index: pageIndex, size: pageSize } = page; @@ -330,6 +338,7 @@ export const AgentsTable = withErrorBoundary( name: agent.name, ip: agent.ip, status: agent.status, + group_config_status: agent.group_config_status, group: checkField(agent.group), os_name: agent, version: agentVersion, @@ -782,20 +791,20 @@ export const AgentsTable = withErrorBoundary( field: 'name', name: 'Name', sortable: true, - width: '15%', + width: '10%', truncateText: true, }, { field: 'ip', name: 'IP', - width: '10%', + width: '8%', truncateText: true, sortable: true, }, { field: 'group', name: 'Group(s)', - width: '20%', + width: '14%', truncateText: true, sortable: true, render: (groups) => (groups !== '-' ? this.renderGroups(groups) : '-'), @@ -804,14 +813,14 @@ export const AgentsTable = withErrorBoundary( field: 'os_name', name: 'OS', sortable: true, - width: '15%', + width: '10%', truncateText: true, render: this.addIconPlatformRender, }, { field: 'node_name', name: 'Cluster node', - width: '10%', + width: '8%', truncateText: true, sortable: true, }, @@ -826,14 +835,14 @@ export const AgentsTable = withErrorBoundary( { field: 'dateAdd', name: 'Registration date', - width: '10%', + width: '8%', truncateText: true, sortable: true, }, { field: 'lastKeepAlive', name: 'Last keep alive', - width: '10%', + width: '8%', truncateText: true, sortable: true, }, @@ -842,9 +851,17 @@ export const AgentsTable = withErrorBoundary( name: 'Status', truncateText: true, sortable: true, - width: '15%', + width: '10%', render: (status) => , }, + { + field: 'group_config_status', + name: 'Synced', + truncateText: true, + sortable: true, + width: '10%', + render: (synced) => , + }, { align: 'right', width: '5%', @@ -1014,11 +1031,6 @@ export const AgentsTable = withErrorBoundary( }, }; - const selection = { - selectable: (agent) => agent.id, - /* onSelectionChange: this.onSelectionChange */ - }; - return ( @@ -1031,8 +1043,6 @@ export const AgentsTable = withErrorBoundary( loading={isLoading} rowProps={getRowProps} cellProps={getCellProps} - /* isSelectable={false} - selection={selection} */ noItemsMessage="No agents found" {...(pagination && { pagination })} /> diff --git a/public/controllers/management/components/management/configuration/utils/wz-fetch.js b/public/controllers/management/components/management/configuration/utils/wz-fetch.js index 5ce3ef50ce..60f506df39 100644 --- a/public/controllers/management/components/management/configuration/utils/wz-fetch.js +++ b/public/controllers/management/components/management/configuration/utils/wz-fetch.js @@ -14,6 +14,7 @@ import { WzRequest } from '../../../../../../react-services/wz-request'; import { replaceIllegalXML } from './xml'; import { getToasts } from '../../../../../../kibana-services'; import { delayAsPromise } from '../../../../../../../common/utils'; +import { AGENT_SYNCED_STATUS } from '../../../../../../../common/constants'; /** * Get configuration for an agent/manager of request sections @@ -480,9 +481,9 @@ export const validateAfterSent = async (node = false) => { export const agentIsSynchronized = async agent => { const isSync = await WzRequest.apiReq( 'GET', - `/agents/${agent.id}/group/is_sync`, {} + `/agents?q=id=${agent.id}&select=group_config_status`, {} ); - return (((((isSync || {}).data || {}).data || {}).affected_items || [])[0] || {}).synced || false; + return isSync?.data?.data?.affected_items?.[0]?.group_config_status == AGENT_SYNCED_STATUS.SYNCED; } /** diff --git a/public/controllers/management/components/management/status/actions-buttons-main.js b/public/controllers/management/components/management/status/actions-buttons-main.js index ce95c92052..9ba6b42efc 100644 --- a/public/controllers/management/components/management/status/actions-buttons-main.js +++ b/public/controllers/management/components/management/status/actions-buttons-main.js @@ -132,7 +132,7 @@ class WzStatusActionButtons extends Component { this.props.updateLoadingStatus(true); this.props.updateSelectedNode(node); - const [agentsCount, agentsCountByManagerNodes] = (await Promise.all([ + const [{connection: agentsCount}, agentsCountByManagerNodes] = (await Promise.all([ this.statusHandler.agentsSummary(), this.statusHandler.clusterAgentsCount() ])).map(response => response?.data?.data); diff --git a/public/controllers/management/components/management/status/status-overview.js b/public/controllers/management/components/management/status/status-overview.js index a2f20feb3d..6818945806 100644 --- a/public/controllers/management/components/management/status/status-overview.js +++ b/public/controllers/management/components/management/status/status-overview.js @@ -94,7 +94,7 @@ export class WzStatusOverview extends Component { this.props.updateLoadingStatus(true); - const [agentsCount, clusterStatus, managerInfo, agentsCountByManagerNodes] = (await Promise.all([ + const [{connection: agentsCount, configuration}, clusterStatus, managerInfo, agentsCountByManagerNodes] = (await Promise.all([ this.statusHandler.agentsSummary(), this.statusHandler.clusterStatus(), this.statusHandler.managerInfo(), @@ -104,6 +104,7 @@ export class WzStatusOverview extends Component { this.props.updateStats({ agentsCountByManagerNodes: agentsCountByManagerNodes.nodes, agentsCount, + agentsSynced: configuration.total ? ((configuration.synced / configuration.total) * 100).toFixed(2) : 0, agentsCoverage: agentsCount.total ? ((agentsCount.active / agentsCount.total) * 100).toFixed(2) : 0, }); diff --git a/public/controllers/management/components/management/status/status-stats.js b/public/controllers/management/components/management/status/status-stats.js index ddfe719192..3bf8394e39 100644 --- a/public/controllers/management/components/management/status/status-stats.js +++ b/public/controllers/management/components/management/status/status-stats.js @@ -32,6 +32,11 @@ export class WzStatusStats extends Component { description: 'Agents coverage', status: 'coverage' }) + this.agentStatus.push({ + color: undefined, + description: 'Synced agents', + status: 'synced' + }) } componentDidMount() { @@ -44,9 +49,18 @@ export class WzStatusStats extends Component { this._isMounted = false; } - render() { + getTitle(status) { const { stats } = this.props.state; + const metric = { + [status]: stats?.agentsCount?.[status], + coverage: `${stats?.agentsCoverage}%`, + synced: `${stats?.agentsSynced}%` + }; + return metric[status]; + } + render() { + return (
@@ -54,7 +68,7 @@ export class WzStatusStats extends Component { {this.agentStatus.map(({color, description, status}) => (