From 7ab242e94892233a0b007646fe01f8877e866f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Wed, 25 May 2022 13:05:58 +0200 Subject: [PATCH 1/4] feat: consistency in custom donut visualizations and agent status, labels and colors - Consistency in custom donut visualizations: - Replaced the visualizations in `Agents` and `Modules/SCA/Inventory` by the `VisualizationBasic` component - Removed the `Pie` component - Consistency in agent status, labels, and colors: - Centralized the agent status, labels, and colors to constants.ts file - Replaced the hardcoded values with the centralized ones - Added the `pending` status to: - Agents stats in Modules directory - Status visualization in `Agents` section - Details panel in `Agents` section - Agents stats in Management/Status - Created a service to get the color and label depending on the agent status. It is shared by the backend and frontend. - Created a component to display the agent status: AgentStatus. It is adapted to the different use cases. - Replaced the old rendering with the new one in some components. Removed unnecessary methods and layout. - Removed deprecated methods and files - Rendering agent status stats when the monitoring job was disabled and the visualization of agent status evolution was in Modules/Security events. `WzVisualize` component. - Removed an API request to get the agent status data - Refactored the API request managing in: - `Modules/SCA/Inventory` - `Agents` - `Management/Status` - Added a new property `error` to `VisualizationBasic` to render the error message coming from `error.message` - Fixed an error when creating a visualization that didn't apply the definitions in `uiStateJSON` property. This is due to a bug in Kibana 7.10.2 - Fixed some typos --- common/constants.ts | 31 ++ common/services/wz_agent_status.ts | 11 + .../components/agents/agent_status.test.tsx | 19 ++ public/components/agents/agent_status.tsx | 16 + public/components/agents/fim/main.tsx | 3 +- public/components/agents/sca/inventory.tsx | 66 ++-- public/components/agents/sca/main.tsx | 5 +- .../components/agents/stats/agent-stats.tsx | 4 +- .../agents/syscollector/inventory.tsx | 5 +- .../common/charts/visualizations/basic.tsx | 8 +- .../components/common/modules/main-agent.tsx | 6 - .../common/modules/main-overview.tsx | 10 - .../modules/overview-current-section.tsx | 13 - .../components/common/welcome/agents-info.js | 24 +- .../common/welcome/agents-welcome.js | 3 +- public/components/d3/pie.js | 193 ------------ public/components/visualize/wz-visualize.js | 60 +--- .../wz-menu/components/wz-agent-info.js | 142 --------- public/components/wz-menu/wz-menu.js | 58 +--- public/controllers/agent/agents.js | 20 +- .../agent/components/agents-preview.js | 298 +++++++----------- .../agent/components/agents-table.js | 47 +-- .../configuration/configuration-overview.js | 3 +- .../configuration/configuration-switch.js | 4 +- .../management/groups/group-agents-table.js | 4 +- .../management/status/actions-buttons-main.js | 16 +- .../management/status/status-agent-info.js | 8 +- .../management/status/status-node-info.js | 2 +- .../management/status/status-overview.js | 31 +- .../management/status/status-stats.js | 63 ++-- .../__snapshots__/stats.test.tsx.snap | 177 +++++++++-- .../agents-selection-table.js | 29 +- .../controllers/overview/components/stats.js | 103 ++---- public/controllers/overview/overview.js | 22 +- public/kibana-integrations/kibana-vis.js | 15 +- public/react-services/action-agents.js | 8 +- public/templates/visualize/dashboards.html | 6 +- .../templates/visualize/overview-welcome.html | 6 +- server/controllers/wazuh-reporting.ts | 6 +- .../overview/overview-general.ts | 4 +- .../overview/overview-office.ts | 4 +- 41 files changed, 546 insertions(+), 1007 deletions(-) create mode 100644 common/services/wz_agent_status.ts create mode 100644 public/components/agents/agent_status.test.tsx create mode 100644 public/components/agents/agent_status.tsx delete mode 100644 public/components/d3/pie.js delete mode 100644 public/components/wz-menu/components/wz-agent-info.js diff --git a/common/constants.ts b/common/constants.ts index 16fd284c77..7276d46c35 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -365,3 +365,34 @@ export const PLUGIN_PLATFORM_URL_GUIDE_TITLE = 'Elastic guide'; export const PLUGIN_PLATFORM_REQUEST_HEADERS = { 'kbn-xsrf': 'kibana' }; + +// UI +export const API_NAME_AGENT_STATUS = { + ACTIVE: 'active', + DISCONNECTED: 'disconnected', + PENDING: 'pending', + NEVER_CONNECTED: 'never_connected', +} + +export const UI_COLOR_AGENT_STATUS = { + [API_NAME_AGENT_STATUS.ACTIVE]: '#007871', + [API_NAME_AGENT_STATUS.DISCONNECTED]: '#BD271E', + [API_NAME_AGENT_STATUS.PENDING]: '#FEC514', + [API_NAME_AGENT_STATUS.NEVER_CONNECTED]: '#646A77', + default: '#000000' +} + +export const UI_LABEL_NAME_AGENT_STATUS = { + [API_NAME_AGENT_STATUS.ACTIVE]: 'Active', + [API_NAME_AGENT_STATUS.DISCONNECTED]: 'Disconnected', + [API_NAME_AGENT_STATUS.PENDING]: 'Pending', + [API_NAME_AGENT_STATUS.NEVER_CONNECTED]: 'Never connected', + default: 'Unknown' +} + +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 +] \ No newline at end of file diff --git a/common/services/wz_agent_status.ts b/common/services/wz_agent_status.ts new file mode 100644 index 0000000000..dbbd6602ac --- /dev/null +++ b/common/services/wz_agent_status.ts @@ -0,0 +1,11 @@ +import { UI_COLOR_AGENT_STATUS, UI_LABEL_NAME_AGENT_STATUS } from '../constants'; + +type AgentStatus = 'active' | 'disconected' | 'pending' | 'never_connected'; + +export function agentStatusColorByAgentStatus(status: AgentStatus): string{ + return UI_COLOR_AGENT_STATUS[status] || UI_COLOR_AGENT_STATUS.default; +} + +export function agentStatusLabelByAgentStatus(status: AgentStatus): string{ + return UI_LABEL_NAME_AGENT_STATUS[status] || UI_LABEL_NAME_AGENT_STATUS.default; +} \ No newline at end of file diff --git a/public/components/agents/agent_status.test.tsx b/public/components/agents/agent_status.test.tsx new file mode 100644 index 0000000000..f1413959da --- /dev/null +++ b/public/components/agents/agent_status.test.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { AgentStatus } from './agent_status'; +import { API_NAME_AGENT_STATUS, UI_COLOR_AGENT_STATUS, UI_LABEL_NAME_AGENT_STATUS, UI_ORDER_AGENT_STATUS } from '../../../common/constants'; + +describe('AgentStatus component', () => { + test.each(UI_ORDER_AGENT_STATUS.map(status => ({ status, color: UI_COLOR_AGENT_STATUS[status], label: UI_LABEL_NAME_AGENT_STATUS[status] })))('Renders status indicator with the its color and the label in lower case - %j', (input) => { + const wrapper = mount(); + expect(wrapper.find('svg').prop('style')).toHaveProperty('color', input.color); + expect(wrapper.find('.euiFlexGroup > span.euiFlexItem.euiFlexItem--flexGrowZero').text()).toEqual(input.label.toLowerCase()) + }); + + it(`Renders status indicator with the its color and a custom label - status: ${API_NAME_AGENT_STATUS.ACTIVE}`, () => { + const label = 'custom_agent'; + const wrapper = mount({label}); + expect(wrapper.find('svg').prop('style')).toHaveProperty('color', UI_COLOR_AGENT_STATUS[API_NAME_AGENT_STATUS.ACTIVE]); + expect(wrapper.find('.euiFlexGroup > span.euiFlexItem.euiFlexItem--flexGrowZero').text()).toEqual(label); + }); +}); diff --git a/public/components/agents/agent_status.tsx b/public/components/agents/agent_status.tsx new file mode 100644 index 0000000000..40a42d0c64 --- /dev/null +++ b/public/components/agents/agent_status.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { EuiToolTip } from '@elastic/eui'; +import { agentStatusColorByAgentStatus, agentStatusLabelByAgentStatus } from '../../../common/services/wz_agent_status'; + +export const AgentStatus = ({ status, children = null, labelProps = {}, style = {} }) => ( + + + + + + + {children || agentStatusLabelByAgentStatus(status).toLowerCase()} + +) \ No newline at end of file diff --git a/public/components/agents/fim/main.tsx b/public/components/agents/fim/main.tsx index e5d5f339a3..242bea4978 100644 --- a/public/components/agents/fim/main.tsx +++ b/public/components/agents/fim/main.tsx @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { PromptNoActiveAgent, PromptNoSelectedAgent } from '../prompts'; import { compose } from 'redux'; import { withAgentSupportModule, withGuard, withUserAuthorizationPrompt } from '../../common/hocs'; +import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; const mapStateToProps = (state) => ({ currentAgentData: state.appStateReducers.currentAgentData, @@ -23,7 +24,7 @@ export const MainFim = compose( (props) => { const agentData = props.currentAgentData && props.currentAgentData.id ? props.currentAgentData : props.agent; - return agentData.status !== 'active'; + return agentData.status !== API_NAME_AGENT_STATUS.ACTIVE; }, () => ), diff --git a/public/components/agents/sca/inventory.tsx b/public/components/agents/sca/inventory.tsx index 623126d9b7..d0da6affd3 100644 --- a/public/components/agents/sca/inventory.tsx +++ b/public/components/agents/sca/inventory.tsx @@ -11,7 +11,6 @@ */ import React, { Component, Fragment } from 'react'; -import { Pie } from "../../d3/pie"; import { EuiFlexItem, EuiFlexGroup, @@ -47,16 +46,16 @@ import { UIErrorSeverity, UILogLevel, } from '../../../react-services/error-orchestrator/types'; -import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { API_NAME_AGENT_STATUS, UI_LOGGER_LEVELS } from '../../../../common/constants'; import { getErrorOrchestrator } from '../../../react-services/common-services'; +import { VisualizationBasic } from '../../common/charts/visualizations/basic'; export class Inventory extends Component { _isMount = false; constructor(props) { super(props); const { agent } = this.props; - this.state = { agent, items: [], itemIdToExpandedRowMap: {}, showMoreInfo: false, loading: false, filters: [], pageTableChecks: {pageIndex: 0} } - this.policies = []; + this.state = { agent, items: [], itemIdToExpandedRowMap: {}, showMoreInfo: false, loading: false, filters: [], pageTableChecks: {pageIndex: 0}, policies: [] } this.suggestions = {}; this.columnsPolicies = [ { @@ -272,31 +271,14 @@ export class Inventory extends Component { try { this._isMount && this.setState({ loading: true }); this.lookingPolicy = false; - const policies = await WzRequest.apiReq( + const {data: {data: {affected_items: policies}}} = await WzRequest.apiReq( 'GET', `/sca/${this.props.agent.id}`, {} ); - this.policies = (((policies || {}).data || {}).data || {}).affected_items || []; - const models = []; - for (let i = 0; i < this.policies.length; i++) { - models.push({ - name: this.policies[i].name, - status: [ - { id: 'pass', label: 'Pass', value: this.policies[i].pass }, - { id: 'fail', label: 'Fail', value: this.policies[i].fail }, - { - id: 'invalid', - label: 'Not applicable', - value: this.policies[i].invalid - } - ] - }); - } - this._isMount && this.setState({ data: models, loading: false }); + this._isMount && this.setState({ loading: false, policies }); } catch (error) { - this.setState({ loading: false }); - this.policies = []; + this.setState({ loading: false, policies: []}); const options: UIErrorLog = { context: `${Inventory.name}.initialize`, @@ -501,40 +483,56 @@ export class Inventory extends Component { )} - {((this.props.agent && (this.props.agent || {}).status !== 'never_connected' && !(this.policies || []).length && !this.state.loading) && + {((this.props.agent && (this.props.agent || {}).status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED && !this.state.policies.length && !this.state.loading) && this.initialize()}> Refresh - + )} - {((this.props.agent && (this.props.agent || {}).status === 'never_connected' && !this.state.loading) && + {((this.props.agent && (this.props.agent || {}).status === API_NAME_AGENT_STATUS.NEVER_CONNECTED && !this.state.loading) && this.initialize()}> Refresh )} - {((this.props.agent && (this.props.agent || {}).os && !this.state.lookingPolicy && (this.policies || []).length > 0 && !this.state.loading) && + {((this.props.agent && (this.props.agent || {}).os && !this.state.lookingPolicy && this.state.policies.length > 0 && !this.state.loading) &&
- {((this.state.data || []).length && + {this.state.policies.length && - {(this.state.data || []).map((pie, idx) => ( + {this.state.policies.map((policy, idx) => ( - - + + + ))} - )} + } diff --git a/public/components/agents/sca/main.tsx b/public/components/agents/sca/main.tsx index 77ace8ce5c..ddcbcbd6b9 100644 --- a/public/components/agents/sca/main.tsx +++ b/public/components/agents/sca/main.tsx @@ -16,6 +16,7 @@ import { connect } from 'react-redux'; import { compose } from 'redux'; import { PromptSelectAgent, PromptNoSelectedAgent } from '../prompts'; import { withGuard, withUserAuthorizationPrompt, withAgentSupportModule } from '../../common/hocs'; +import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; const mapStateToProps = (state) => ({ currentAgentData: state.appStateReducers.currentAgentData, @@ -43,10 +44,10 @@ export const MainSca = compose( withGuard( ({ currentAgentData, agent }) => { const agentData = currentAgentData && currentAgentData.id ? currentAgentData : agent; - return agentData.status === 'never_connected'; + return agentData.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED; }, () => ( - + ) ), withUserAuthorizationPrompt((props) => { diff --git a/public/components/agents/stats/agent-stats.tsx b/public/components/agents/stats/agent-stats.tsx index 5e3ce31858..f58b63992c 100644 --- a/public/components/agents/stats/agent-stats.tsx +++ b/public/components/agents/stats/agent-stats.tsx @@ -32,7 +32,7 @@ import { UILogLevel, UIErrorSeverity, } from '../../../react-services/error-orchestrator/types'; -import { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { API_NAME_AGENT_STATUS, UI_LOGGER_LEVELS } from '../../../../common/constants'; import { getErrorOrchestrator } from '../../../react-services/common-services'; const tableColumns = [ @@ -107,7 +107,7 @@ export const MainAgentStats = compose( {action: 'agent:read', resource: `agent:id:${agent.id}`}, ...(agent.group || []).map(group => ({ action: 'agent:read', resource: `agent:group:${group}` })) ]]), - withGuard(({agent}) => agent.status !== 'active', PromptNoActiveAgentWithoutSelect), + withGuard(({agent}) => agent.status !== API_NAME_AGENT_STATUS.ACTIVE, PromptNoActiveAgentWithoutSelect), withGuard(({agent}) => { const [major, minor, patch] = agent.version.replace('Wazuh v','').split('.').map(value => parseInt(value)); return !(major >= 4 && minor >= 2 && patch >= 0) diff --git a/public/components/agents/syscollector/inventory.tsx b/public/components/agents/syscollector/inventory.tsx index 5887cac82d..77c6e056f6 100644 --- a/public/components/agents/syscollector/inventory.tsx +++ b/public/components/agents/syscollector/inventory.tsx @@ -15,9 +15,10 @@ import { EuiEmptyPrompt, EuiButton, EuiFlexGroup, EuiFlexItem, EuiCallOut } from import { InventoryMetrics } from './components/syscollector-metrics'; import { SyscollectorTable } from './components/syscollector-table'; import { processColumns, portsColumns, packagesColumns } from './columns'; +import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; export function SyscollectorInventory({ agent }) { - if (agent && agent.status === 'never_connected') { + if (agent && agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED) { return ( - {agent && agent.status === 'disconnected' && ( + {agent && agent.status === API_NAME_AGENT_STATUS.DISCONNECTED && ( React.node) errorTitle?: string errorMessage?: string | (() => React.node) + error?: {message: string} } const chartTypes = { @@ -32,7 +33,8 @@ export const VisualizationBasic = ({ noDataTitle = 'No data', noDataMessage, errorTitle = 'Error', - errorMessage + errorMessage, + error }: VisualizationBasicProps) => { const { width, height } = typeof size === 'object' ? size : { width: size, height: size }; @@ -44,12 +46,12 @@ export const VisualizationBasic = ({
) - }else if(errorMessage){ + }else if(errorMessage || error?.message){ visualization = ( {errorTitle}} - body={errorMessage} + body={errorMessage || error?.message} /> ) }else if(!data || (Array.isArray(data) && !data.length)){ diff --git a/public/components/common/modules/main-agent.tsx b/public/components/common/modules/main-agent.tsx index f303393650..7cab6c416f 100644 --- a/public/components/common/modules/main-agent.tsx +++ b/public/components/common/modules/main-agent.tsx @@ -111,12 +111,6 @@ export class MainModuleAgent extends Component { this.setState({ showAgentInfo: !this.state.showAgentInfo }); } - color = (status, hex = false) => { - if (status.toLowerCase() === 'active') { return hex ? '#017D73' : 'success'; } - else if (status.toLowerCase() === 'disconnected') { return hex ? '#BD271E' : 'danger'; } - else if (status.toLowerCase() === 'never connected') { return hex ? '#98A2B3' : 'subdued'; } - } - async startReport() { this.setState({ loadingReport: true }); const syscollectorFilters: any[] = []; diff --git a/public/components/common/modules/main-overview.tsx b/public/components/common/modules/main-overview.tsx index 9d498f3901..bc936b168f 100644 --- a/public/components/common/modules/main-overview.tsx +++ b/public/components/common/modules/main-overview.tsx @@ -50,16 +50,6 @@ export const MainModuleOverview = connect(mapStateToProps)(class MainModuleOverv }; } - getBadgeColor(agentStatus) { - if (agentStatus.toLowerCase() === 'active') { - return 'secondary'; - } else if (agentStatus.toLowerCase() === 'disconnected') { - return '#BD271E'; - } else if (agentStatus.toLowerCase() === 'never connected') { - return 'default'; - } - } - setGlobalBreadcrumb() { const currentAgent = store.getState().appStateReducers.currentAgentData; if (WAZUH_MODULES[this.props.section]) { diff --git a/public/components/common/modules/overview-current-section.tsx b/public/components/common/modules/overview-current-section.tsx index dda8212d90..4e8413f4bb 100644 --- a/public/components/common/modules/overview-current-section.tsx +++ b/public/components/common/modules/overview-current-section.tsx @@ -11,17 +11,11 @@ * Find more information about this on the LICENSE file. */ import React, { Component } from 'react'; -import { - EuiTitle, - EuiBadge, - EuiToolTip -} from '@elastic/eui'; import { updateGlobalBreadcrumb } from '../../../redux/actions/globalBreadcrumbActions'; import { updateCurrentTab } from '../../../redux/actions/appStateActions'; import store from '../../../redux/store'; import { connect } from 'react-redux'; import { WAZUH_MODULES } from '../../../../common/wazuh-modules'; -import { AppNavigate } from '../../../react-services/app-navigate'; class WzCurrentOverviewSection extends Component { constructor(props) { @@ -30,13 +24,6 @@ class WzCurrentOverviewSection extends Component { }; } - - getBadgeColor(agentStatus){ - if (agentStatus.toLowerCase() === 'active') { return 'secondary'; } - else if (agentStatus.toLowerCase() === 'disconnected') { return '#BD271E'; } - else if (agentStatus.toLowerCase() === 'never connected') { return 'default'; } - } - setGlobalBreadcrumb() { const currentAgent = store.getState().appStateReducers.currentAgentData; diff --git a/public/components/common/welcome/agents-info.js b/public/components/common/welcome/agents-info.js index 6af31d66a4..1a5a04cc26 100644 --- a/public/components/common/welcome/agents-info.js +++ b/public/components/common/welcome/agents-info.js @@ -24,6 +24,7 @@ import { formatUIDate } from '../../../react-services/time-service'; import WzTextWithTooltipIfTruncated from '../wz-text-with-tooltip-if-truncated'; import { WzStat } from '../../wz-stat'; import { GroupTruncate } from '../util/agent-group-truncate' +import { AgentStatus } from '../../agents/agent_status'; export class AgentInfo extends Component { constructor(props) { @@ -79,27 +80,6 @@ export class AgentInfo extends Component { ) } - - color = (status, hex = false) => { - if (status.toLowerCase() === 'active') { return hex ? '#017D73' : 'success'; } - else if (status.toLowerCase() === 'disconnected') { return hex ? '#BD271E' : 'danger'; } - else if (status.toLowerCase() === 'never connected') { return hex ? '#98A2B3' : 'subdued'; } - } - - addHealthRender(agent, style) { - // this was rendered with a EuiHealth, but EuiHealth has a div wrapper, and this section is rendered within a

tag.

tags aren't allowed within

tags. - return ( - - - - - {this.props.agent.status} - - ) - } - addGroupsRender(agent) { // this was rendered with a EuiHealth, but EuiHealth has a div wrapper, and this section is rendered within a

tag.

tags aren't allowed within

tags. return ( @@ -142,7 +122,7 @@ export class AgentInfo extends Component { ) : item.description === 'Operating system' ? ( this.addTextPlatformRender(this.props.agent, item.style) ) : item.description === 'Status' ? ( - this.addHealthRender(this.props.agent, item.style) + ) : ( {checkField(item.title)} diff --git a/public/components/common/welcome/agents-welcome.js b/public/components/common/welcome/agents-welcome.js index 5f6284fb01..0128666dbb 100644 --- a/public/components/common/welcome/agents-welcome.js +++ b/public/components/common/welcome/agents-welcome.js @@ -53,6 +53,7 @@ import { getAngularModule } from '../../../kibana-services'; import { hasAgentSupportModule } from '../../../react-services/wz-agents'; import { withErrorBoundary, withReduxProvider } from '../hocs'; import { compose } from 'redux'; +import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; export const AgentsWelcome = compose( withReduxProvider, @@ -508,7 +509,7 @@ class AgentsWelcome extends Component { render() { const title = this.renderTitle(); const upgradeButton = this.renderUpgradeButton(); - if (this.props.agent.status === 'never_connected') { + if (this.props.agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED) { return ( d.value) - .sort(null); - - const width = this.props.width; - const height = this.props.height; - const data = createPie(this.props.data); - const colors = this.props.colors || [ - '#6eadc1', - '#57c17b', - '#6f87d8', - '#663db8', - '#bc52bc', - '#9e3533', - '#daa05d' - ]; - return ( - - - {data.map((d, i) => ( - - ))} - - - {data.map((d, i) => ( - - - {this.props.legendAction && - {this.props.legendAction(d.data.label)}} x="15" y="10" style={{ fontSize: 12, cursor: "pointer" }}> - Filter {d.data.label.toLowerCase()} agents - {d.data.label} - - || - - - {d.data.label} - - } - - ))} - - - ); - } -} - -class Slice extends React.Component { - constructor(props) { - super(props); - this.state = { isHovered: false }; - this.onMouseOver = this.onMouseOver.bind(this); - this.onMouseOut = this.onMouseOut.bind(this); - } - - onMouseMove(div, html, ev) { - div - .html(html) - .style('display', 'block') - .style('z-index', 100) - .style('opacity', 1) - .style('left', ev.pageX + 10 + 'px') - .style('top', ev.pageY - 15 + 'px'); - } - - onMouseOver(div) { - this.setState({ isHovered: true }); - div - .transition() - .duration(100) - .style('display', 'block') - .style('z-index', 100) - .style('opacity', 1); - } - - onMouseOut(div) { - this.setState({ isHovered: false }); - div - .transition() - .duration(100) - .style('z-index', -1) - .style('opacity', 0); - } - - lightenDarkenColor(col, amt) { - let usePound = false; - if (col[0] == '#') { - col = col.slice(1); - usePound = true; - } - const num = parseInt(col, 16); - let r = (num >> 16) + amt; - if (r > 255) r = 255; - else if (r < 0) r = 0; - let b = ((num >> 8) & 0x00ff) + amt; - if (b > 255) b = 255; - else if (b < 0) b = 0; - let g = (num & 0x0000ff) + amt; - if (g > 255) g = 255; - else if (g < 0) g = 0; - return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16); - } - - render() { - let { - value, - label, - fill, - innerRadius = 0, - outerRadius, - cornerRadius, - padAngle, - ...props - } = this.props; - if (this.state.isHovered) { - fill = this.lightenDarkenColor(fill, 50); - } - const newValue = { ...value, endAngle: value.endAngle - 0.01 }; - let arc = d3 - .svg.arc() - .innerRadius(innerRadius) - .outerRadius(outerRadius) - .cornerRadius(cornerRadius) - .padAngle(0.01); - - let div = d3.select('.tooltip-donut'); - const html = `${newValue.data.value} ${newValue.data.label}`; - - return ( - this.onMouseOver(div)} - onMouseOut={() => this.onMouseOut(div)} - onMouseMove={ev => this.onMouseMove(div, html, ev)} - {...props} - > - - - {label} - - - ); - } -} diff --git a/public/components/visualize/wz-visualize.js b/public/components/visualize/wz-visualize.js index 651fb58646..d9f6d64fa8 100644 --- a/public/components/visualize/wz-visualize.js +++ b/public/components/visualize/wz-visualize.js @@ -74,34 +74,6 @@ export const WzVisualize = compose( this._isMount = true; visHandler.removeAll(); this.agentsStatus = false; - if (!this.monitoringEnabled) { - const data = await this.wzReq.apiReq('GET', '/agents/summary/status', {}); - const result = ((data || {}).data || {}).data || false; - if (result) { - this.agentsStatus = [ - { - title: 'Total', - description: result.total, - }, - { - title: 'Active', - description: result.active, - }, - { - title: 'Disconnected', - description: result.disconnected, - }, - { - title: 'Never Connected', - description: result['never_connected'], - }, - { - title: 'Agents coverage', - description: (result.total ? (result.active / result.total) * 100 : 0) + '%', - }, - ]; - } - } } async componentDidUpdate(prevProps) { @@ -230,29 +202,15 @@ export const WzVisualize = compose( aria-label="Expand" /> -

- {(vis.id !== 'Wazuh-App-Overview-General-Agents-status' || - (vis.id === 'Wazuh-App-Overview-General-Agents-status' && - this.monitoringEnabled)) && ( - - - - )} - {vis.id === 'Wazuh-App-Overview-General-Agents-status' && - !this.monitoringEnabled && ( - - - - )} +
+ + +
diff --git a/public/components/wz-menu/components/wz-agent-info.js b/public/components/wz-menu/components/wz-agent-info.js deleted file mode 100644 index 41b96365c8..0000000000 --- a/public/components/wz-menu/components/wz-agent-info.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Wazuh app - React component for showing agent fields such as IP, ID, name, - * version, OS, registration date, last keep alive. - * - * 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 { - EuiStat, - EuiFlexItem, - EuiFlexGroup, - EuiHealth -} from '@elastic/eui'; -import { WzRequest } from '../../../react-services/wz-request'; - -export class AgentInfo extends Component { - constructor(props) { - super(props); - - this.agent = { - id: '002', - status: 'Active', - ip: 'X.X.X.X', - version: 'Wazuh v3.12.3', - name: 'CentOS Linux 7.6' - }; - - this.state = {}; - - this - - } - - async componentDidMount() { - const managerVersion = await WzRequest.apiReq('GET', '/', {}); - - this.setState({ - managerVersion: (((managerVersion || {}).data || {}).data || {}).api_version || {} - }); - } - - getPlatformIcon(agent) { - let icon = false; - const os = (agent || {}).os; - - if (((os || {}).uname || '').includes('Linux')) { - icon = 'linux'; - } else if ((os || {}).platform === 'windows') { - icon = 'windows'; - } else if ((os || {}).platform === 'darwin') { - icon = 'apple'; - } - - return - } - - color = (status, hex = false) => { - if (status.toLowerCase() === 'active') { return hex ? '#017D73' : 'success'; } - else if (status.toLowerCase() === 'disconnected') { return hex ? '#BD271E' : 'danger'; } - else if (status.toLowerCase() === 'never connected') { return hex ? '#98A2B3' : 'subdued'; } - } - - addHealthRender(agent) { - // this was rendered with a EuiHealth, but EuiHealth has a div wrapper, and this section is rendered within a

tag.

tags aren't allowed within

tags. - return ( - - - - - {this.agent.status} - - ) - } - - buildStats(items) { - const checkField = field => { - return field !== undefined || field ? field : '-'; - }; - const stats = items.map(item => { - return ( - - - {checkField(item.title)} - - ) - } - description={item.description} - titleSize="xs" - /> - - ); - }); - return stats; - } - - render() { - const stats = this.buildStats([ - { title: this.agent.id, description: 'ID', style: { maxWidth: 100 } }, - { title: this.agent.status, description: 'Status', style: { maxWidth: 200 } }, - { title: this.agent.version, description: 'Version' }, - { - title: this.agent.name, - description: 'Operating system', - style: { } - } - ]); - - return ( - - - {stats} - - - ); - } -} diff --git a/public/components/wz-menu/wz-menu.js b/public/components/wz-menu/wz-menu.js index 3ee6976792..f1922c7057 100644 --- a/public/components/wz-menu/wz-menu.js +++ b/public/components/wz-menu/wz-menu.js @@ -50,6 +50,7 @@ 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 { getThemeAssetURL, getAssetURL } from '../../utils/assets'; +import { AgentStatus } from '../agents/agent_status'; const sections = { @@ -614,44 +615,6 @@ export const WzMenu = withWindowSize(class WzMenu extends Component { }); }; - color = (status, hex = false) => { - if (status.toLowerCase() === 'active') { return hex ? '#017D73' : 'success'; } - else if (status.toLowerCase() === 'disconnected') { return hex ? '#BD271E' : 'danger'; } - else if (status.toLowerCase() === 'never connected') { return hex ? '#98A2B3' : 'subdued'; } - } - - formatAgentStatus = (status) => { - if (status === 'active') { - return "Active"; - } - if (status === 'disconnected') { - return "Disconnected"; - } - if (status === 'never_connected') { - return "Never connected"; - } - } - - addHealthRender(agent) { - // this was rendered with a EuiHealth, but EuiHealth has a div wrapper, and this section is rendered within a

tag.

tags aren't allowed within

tags. - return ( - - - - - - - - - {agent.name} - - - - ) - } - removeSelectedAgent() { store.dispatch(updateCurrentAgentData({})); if (window.location.href.includes("/agents?")) { @@ -669,12 +632,6 @@ export const WzMenu = withWindowSize(class WzMenu extends Component { }); } - getBadgeColor(agentStatus) { - if (agentStatus.toLowerCase() === 'active') { return 'secondary'; } - else if (agentStatus.toLowerCase() === 'disconnected') { return '#BD271E'; } - else if (agentStatus.toLowerCase() === 'never connected') { return 'default'; } - } - thereAreSelectors() { return ((AppState.getAPISelector() && this.state.currentAPI && this.state.APIlist && this.state.APIlist.length > 1) || (!this.state.currentAPI) @@ -899,15 +856,12 @@ export const WzMenu = withWindowSize(class WzMenu extends Component { {/*this.state.hover === 'overview' */this.state.isOverviewPopoverOpen && currentAgent.id && ( - {/* - - - {currentAgent.id} - - - */} - {this.addHealthRender(currentAgent)} + + + {currentAgent.name} + + diff --git a/public/controllers/agent/agents.js b/public/controllers/agent/agents.js index e80023afa3..b5ae910752 100644 --- a/public/controllers/agent/agents.js +++ b/public/controllers/agent/agents.js @@ -28,7 +28,7 @@ import { ErrorHandler } from '../../react-services/error-handler'; import { GroupHandler } from '../../react-services/group-handler'; import store from '../../redux/store'; import { updateGlobalBreadcrumb } from '../../redux/actions/globalBreadcrumbActions'; -import { WAZUH_ALERTS_PATTERN } from '../../../common/constants'; +import { API_NAME_AGENT_STATUS, WAZUH_ALERTS_PATTERN } from '../../../common/constants'; import { getDataPlugin } from '../../kibana-services'; import { hasAgentSupportModule } from '../../react-services/wz-agents'; import { UI_LOGGER_LEVELS } from '../../../common/constants'; @@ -162,12 +162,6 @@ export class AgentsController { this.changeAgent = false; this.$scope.switchTab = (tab, force = false) => this.switchTab(tab, force); - - this.$scope.getAgentStatusClass = (agentStatus) => (agentStatus === 'active' ? 'teal' : 'red'); - - this.$scope.formatAgentStatus = (agentStatus) => { - return ['active', 'disconnected'].includes(agentStatus) ? agentStatus : 'never connected'; - }; this.$scope.getAgent = async (newAgentId) => this.getAgent(newAgentId); this.$scope.goGroups = (agent, group) => this.goGroups(agent, group); this.$scope.downloadCsv = async (path, fileName, filters = []) => @@ -726,14 +720,6 @@ export class AgentsController { } } - checkStatusAgent() { - if (this.$scope.agent.status !== 'Active') { - return false; - } else if (this.$scope.agent.status === 'Active') { - return true; - } - } - // Agent data /** @@ -962,7 +948,7 @@ export class AgentsController { async launchRootcheckScan() { try { - const isActive = ((this.$scope.agent || {}).status || '') === 'active'; + const isActive = ((this.$scope.agent || {}).status || '') === API_NAME_AGENT_STATUS.ACTIVE; if (!isActive) { throw new Error('Agent is not active'); } @@ -988,7 +974,7 @@ export class AgentsController { async launchSyscheckScan() { try { - const isActive = ((this.$scope.agent || {}).status || '') === 'active'; + const isActive = ((this.$scope.agent || {}).status || '') === API_NAME_AGENT_STATUS.ACTIVE; if (!isActive) { throw new Error('Agent is not active'); } diff --git a/public/controllers/agent/components/agents-preview.js b/public/controllers/agent/components/agents-preview.js index 5823b31cf7..2a4be45003 100644 --- a/public/controllers/agent/components/agents-preview.js +++ b/public/controllers/agent/components/agents-preview.js @@ -23,7 +23,6 @@ import { EuiToolTip, EuiCard, } from '@elastic/eui'; -import { Pie } from '../../../components/d3/pie'; import { AgentsTable } from './agents-table'; import { WzRequest } from '../../../react-services/wz-request'; import KibanaVis from '../../../kibana-integrations/kibana-vis'; @@ -42,13 +41,11 @@ 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 } from '../../../../common/constants'; +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'; - -const FILTER_ACTIVE = 'active'; -const FILTER_DISCONNECTED = 'disconnected'; -const FILTER_NEVER_CONNECTED = 'never_connected'; +import { VisualizationBasic } from '../../../components/common/charts/visualizations/basic'; +import { agentStatusColorByAgentStatus, agentStatusLabelByAgentStatus } from '../../../../common/services/wz_agent_status'; export const AgentsPreview = compose( withErrorBoundary, @@ -66,22 +63,22 @@ export const AgentsPreview = compose( constructor(props) { super(props); this.state = { - data: [], loading: false, showAgentsEvolutionVisualization: false, - agentTableFilters: [] + agentTableFilters: [], + agentStatusSummary: {} }; this.wazuhConfig = new WazuhConfig(); - this.agentStatusLabelToIDMap = { - Active: 'active', - Disconnected: 'disconnected', - 'Never connected': 'never_connected', - }; + this.agentStatus = UI_ORDER_AGENT_STATUS.map(agentStatus => ({ + status: agentStatus, + label: agentStatusLabelByAgentStatus(agentStatus), + color: agentStatusColorByAgentStatus(agentStatus) + })); } async componentDidMount() { this._isMount = true; - this.getSummary(); + this.fetchAgentStatusDetailsData(); if (this.wazuhConfig.getConfig()['wazuh.monitoring.enabled']) { this._isMount && this.setState({ showAgentsEvolutionVisualization: true @@ -94,7 +91,6 @@ export const AgentsPreview = compose( }); const filterHandler = new FilterHandler(AppState.getCurrentPattern()); await VisFactoryHandler.buildOverviewVisualizations(filterHandler, 'general', null); - } } @@ -102,9 +98,6 @@ export const AgentsPreview = compose( this._isMount = false; } - agentStatusLabelToID(label) { - return this.agentStatusLabelToIDMap[label]; - } groupBy = function (arr) { return arr.reduce(function (prev, item) { @@ -114,40 +107,27 @@ export const AgentsPreview = compose( }, {}); }; - async getSummary() { + async fetchAgentStatusDetailsData(){ try { this.setState({ loading: true }); - const summaryData = await WzRequest.apiReq('GET', '/agents/summary/status', {}); - this.summary = summaryData.data.data; - this.totalAgents = this.summary.total; - const model = [ - { id: 'active', label: 'Active', value: this.summary['active'] || 0 }, - { id: 'disconnected', label: 'Disconnected', value: this.summary['disconnected'] || 0 }, - { - id: 'neverConnected', - label: 'Never connected', - value: this.summary['never_connected'] || 0, - }, - ]; - this.setState({ data: model }); - this.agentsCoverity = this.totalAgents - ? ((this.summary['active'] || 0) / this.totalAgents) * 100 - : 0; - const lastAgent = await WzRequest.apiReq('GET', '/agents', { + const {data: {data: agentStatusSummary}} = 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' }, }); - this.lastAgent = lastAgent.data.data.affected_items[0]; - this.mostActiveAgent = await this.props.tableProps.getMostActive(); - const osresult = await WzRequest.apiReq('GET', '/agents/summary/os', {}); - this.platforms = this.groupBy(osresult.data.data.affected_items); - const platformsModel = []; - for (let [key, value] of Object.entries(this.platforms)) { - platformsModel.push({ id: key, label: key, value: value }); - } - this._isMount && this.setState({ platforms: platformsModel, loading: false }); + const agentMostActive = await this.props.tableProps.getMostActive(); + + this.setState({ + loading: false, + lastRegisteredAgent, + agentStatusSummary, + agentsActiveCoverage: ((agentStatusSummary.active/agentStatusSummary.total)*100).toFixed(2), + agentMostActive + }); } catch (error) { + this.setState({loading: false}); const options = { - context: `${AgentsPreview.name}.getSummary`, + context: `${AgentsPreview.name}.fetchAgentStatusDetailsData`, level: UI_LOGGER_LEVELS.ERROR, severity: UI_ERROR_SEVERITIES.BUSINESS, store: true, @@ -158,6 +138,7 @@ export const AgentsPreview = compose( }, }; getErrorOrchestrator().handleError(options); + throw error; } } @@ -165,169 +146,118 @@ export const AgentsPreview = compose( this._isMount && this.setState({ agentTableFilters: [] }); } - showLastAgent() { - this.props.tableProps.showAgent(this.lastAgent); + showAgent(agent) { + agent && this.props.tableProps.showAgent(agent); } - showMostActiveAgent() { - this.mostActiveAgent.name ? this.props.tableProps.showAgent(this.mostActiveAgent) : ''; - } - - showAgentsWithFilters(filter) { + filterAgentByStatus(status) { this._isMount && this.setState({ - agentTableFilters: [{ field: 'q', value: `status=${filter}` }], + agentTableFilters: [{ field: 'q', value: `status=${status}` }], }); } render() { - const colors = ['#017D73', '#bd271e', '#69707D']; return ( - {(this.state.loading && ( + {(this.state.loading && ( )) || ( - + <> - {this.totalAgents > 0 && ( - - - this._isMount && - this.setState({ - agentTableFilters: [ - { - field: 'q', - value: `status=${this.agentStatusLabelToID(status)}`, - }, - ], - }) + + ({ + 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 && ( + + + this.showAgent(this.state.lastRegisteredAgent)}> + {this.state.lastRegisteredAgent.name} + + } - width={300} - height={150} - data={this.state.data} - colors={colors} + titleSize="s" + description="Last registered agent" + titleColor="primary" + /> + + )} + {this.state.agentMostActive && ( + + + this.showAgent(this.state.agentMostActive)}> + {this.state.agentMostActive.name || '-'} + + + } + className="last-agents-link" + titleSize="s" + description="Most active agent" + titleColor="primary" /> )} - {this.totalAgents > 0 && ( - - - {this.summary && ( - - - - this.showAgentsWithFilters(FILTER_ACTIVE)}> - {this.state.data[0].value} - - - } - titleSize={'s'} - description="Active" - titleColor="secondary" - className="white-space-nowrap" - /> - - - - - this.showAgentsWithFilters(FILTER_DISCONNECTED) - } - > - {this.state.data[1].value} - - - } - titleSize={'s'} - description="Disconnected" - titleColor="danger" - className="white-space-nowrap" - /> - - - - - this.showAgentsWithFilters(FILTER_NEVER_CONNECTED) - } - > - {this.state.data[2].value} - - - } - titleSize={'s'} - description="Never connected" - titleColor="subdued" - className="white-space-nowrap" - /> - - - - - - )} - - {this.lastAgent && ( - - - this.showLastAgent()}> - {this.lastAgent.name} - - - } - titleSize="s" - description="Last registered agent" - titleColor="primary" - /> - - )} - {this.mostActiveAgent && ( - - - this.showMostActiveAgent()}> - {this.mostActiveAgent.name || '-'} - - - } - className="last-agents-link" - titleSize="s" - description="Most active agent" - titleColor="primary" - /> - - )} - - - - )} - + )} {this.state.showAgentsEvolutionVisualization && ( formatUIDate(date)} - reload={() => this.getSummary()} + reload={() => this.fetchAgentStatusDetailsData()} /> diff --git a/public/controllers/agent/components/agents-table.js b/public/controllers/agent/components/agents-table.js index 6f9fd9af6b..82d6c5e512 100644 --- a/public/controllers/agent/components/agents-table.js +++ b/public/controllers/agent/components/agents-table.js @@ -43,9 +43,10 @@ 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 { UI_LOGGER_LEVELS } from '../../../../common/constants'; +import { API_NAME_AGENT_STATUS, 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 { AgentStatus } from '../../../components/agents/agent_status'; export const AgentsTable = withErrorBoundary( class AgentsTable extends Component { @@ -74,7 +75,7 @@ export const AgentsTable = withErrorBoundary( label: 'status', description: 'Filter by agent connection status', operators: ['=', '!='], - values: ['active', 'disconnected', 'never_connected', 'pending'], + values: UI_ORDER_AGENT_STATUS, }, { type: 'q', @@ -355,7 +356,7 @@ export const AgentsTable = withErrorBoundary( />   - {agent.status !== 'never_connected' && ( + {agent.status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED && ( { @@ -402,26 +403,6 @@ export const AgentsTable = withErrorBoundary( ); } - addHealthStatusRender(status) { - const color = (status) => { - if (status.toLowerCase() === 'active') { - return 'success'; - } else if (status.toLowerCase() === 'disconnected') { - return 'danger'; - } else if (status.toLowerCase() === 'never_connected') { - return 'subdued'; - } - }; - - return ( - - - {status === 'never_connected' ? 'never connected' : status} - - - ); - } - reloadAgent = () => { this._isMount && this.setState({ @@ -509,11 +490,11 @@ export const AgentsTable = withErrorBoundary( (selectedItems.length > 0 && selectedItems.filter((item) => item.outdated).length === 0) || (selectedItems.length > 0 && selectedItems.filter((item) => item.upgrading).length > 0) || (selectedItems.length > 0 && - selectedItems.filter((item) => item.status === 'Active').length === 0) || + selectedItems.filter((item) => item.status === API_NAME_AGENT_STATUS.ACTIVE).length === 0) || (selectedItems.length > 0 && - selectedItems.filter((item) => item.status === 'Active').length === 0 && - selectedItems.filter((item) => item.status === 'Disconnected').length > 0) || - selectedItems.filter((item) => item.outdated && item.status === 'Active').length === 0 + selectedItems.filter((item) => item.status === API_NAME_AGENT_STATUS.ACTIVE).length === 0 && + selectedItems.filter((item) => item.status === API_NAME_AGENT_STATUS.DISCONNECTED).length > 0) || + selectedItems.filter((item) => item.outdated && item.status === API_NAME_AGENT_STATUS.ACTIVE).length === 0 ) { return; } @@ -522,7 +503,7 @@ export const AgentsTable = withErrorBoundary( Upgrade{' '} - {selectedItems.filter((item) => item.outdated && item.status === 'Active').length}{' '} + {selectedItems.filter((item) => item.outdated && item.status === API_NAME_AGENT_STATUS.ACTIVE).length}{' '} agents @@ -535,7 +516,7 @@ export const AgentsTable = withErrorBoundary( if ( selectedItems.length > 0 && avaibleAgents.filter( - (agent) => agent.version !== 'Wazuh ' + managerVersion && agent.status === 'Active' + (agent) => agent.version !== 'Wazuh ' + managerVersion && agent.status === API_NAME_AGENT_STATUS.ACTIVE ).length === 0 ) { return; @@ -555,7 +536,7 @@ export const AgentsTable = withErrorBoundary( if ( selectedItems.length === 0 || - selectedItems.filter((item) => item.status === 'Active').length === 0 + selectedItems.filter((item) => item.status === API_NAME_AGENT_STATUS.ACTIVE).length === 0 ) { return; } @@ -563,7 +544,7 @@ export const AgentsTable = withErrorBoundary( return ( - Restart {selectedItems.filter((item) => item.status === 'Active').length} agents + Restart {selectedItems.filter((item) => item.status === API_NAME_AGENT_STATUS.ACTIVE).length} agents ); @@ -574,7 +555,7 @@ export const AgentsTable = withErrorBoundary( if ( (selectedItems.length > 0 && - avaibleAgents.filter((item) => item.status === 'Active').length === 0 && + avaibleAgents.filter((item) => item.status === API_NAME_AGENT_STATUS.ACTIVE).length === 0 && selectedItems.length === 0) || agentActive === 0 ) { @@ -862,7 +843,7 @@ export const AgentsTable = withErrorBoundary( truncateText: true, sortable: true, width: '15%', - render: this.addHealthStatusRender, + render: (status) => , }, { align: 'right', diff --git a/public/controllers/management/components/management/configuration/configuration-overview.js b/public/controllers/management/components/management/configuration/configuration-overview.js index c50436084d..bfc6af445d 100644 --- a/public/controllers/management/components/management/configuration/configuration-overview.js +++ b/public/controllers/management/components/management/configuration/configuration-overview.js @@ -32,6 +32,7 @@ import configurationSettingsGroup from './configuration-settings'; import { connect } from 'react-redux'; import { isString, isFunction } from './utils/utils'; import { WzButtonPermissions } from '../../../../../components/common/permissions/button'; +import { API_NAME_AGENT_STATUS } from '../../../../../../common/constants'; const columns = [ { @@ -136,7 +137,7 @@ class WzConfigurationOverview extends Component { )} - {this.props.agent.id !== '000' && this.props.agent.status === 'active' && ( + {this.props.agent.id !== '000' && this.props.agent.status === API_NAME_AGENT_STATUS.ACTIVE && ( ({ action: 'agent:read', resource: `agent:group:${group}` })) ]]), //TODO: this need cluster:read permission but manager/cluster is managed in WzConfigurationSwitch component - withRenderIfOrWrapped((props) => props.agent.status === 'never_connected', WzAgentNeverConnectedPrompt), + withRenderIfOrWrapped((props) => props.agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED, WzAgentNeverConnectedPrompt), connect( mapStateToProps, mapDispatchToProps diff --git a/public/controllers/management/components/management/groups/group-agents-table.js b/public/controllers/management/components/management/groups/group-agents-table.js index f02b5dbcce..556bd5e533 100644 --- a/public/controllers/management/components/management/groups/group-agents-table.js +++ b/public/controllers/management/components/management/groups/group-agents-table.js @@ -32,7 +32,7 @@ import { getAgentFilterValues } from './get-agents-filters-values'; import { TableWzAPI } from '../../../../../components/common/tables'; import { WzButtonPermissions } from '../../../../../components/common/permissions/button'; import { WzButtonPermissionsModalConfirm } from '../../../../../components/common/buttons'; -import { UI_LOGGER_LEVELS } from '../../../../../../common/constants'; +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'; @@ -47,7 +47,7 @@ class WzGroupAgentsTable extends Component { label: 'status', description: 'Filter by agent connection status', operators: ['=', '!='], - values: ['active', 'disconnected', 'never_connected', 'pending'], + values: UI_ORDER_AGENT_STATUS, }, { type: 'q', 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 275ae0dab8..ce95c92052 100644 --- a/public/controllers/management/components/management/status/actions-buttons-main.js +++ b/public/controllers/management/components/management/status/actions-buttons-main.js @@ -132,17 +132,15 @@ class WzStatusActionButtons extends Component { this.props.updateLoadingStatus(true); this.props.updateSelectedNode(node); - const agentSummaryResponse = await this.statusHandler.agentsSummary(); - const agentsCountResponse = await this.statusHandler.clusterAgentsCount(); + const [agentsCount, agentsCountByManagerNodes] = (await Promise.all([ + this.statusHandler.agentsSummary(), + this.statusHandler.clusterAgentsCount() + ])).map(response => response?.data?.data); - const { active, disconnected, never_connected, total } = agentSummaryResponse.data.data; this.props.updateStats({ - agentsCount: agentsCountResponse.data.data.nodes, - agentsCountActive: active, - agentsCountDisconnected: disconnected, - agentsCountNeverConnected: never_connected, - agentsCountTotal: total, - agentsCoverity: total ? (active / total) * 100 : 0 + agentsCountByManagerNodes: agentsCountByManagerNodes.nodes, + agentsCount, + agentsCoverage: agentsCount.total ? ((agentsCount.active / agentsCount.total) * 100).toFixed(2) : 0, }); const daemons = await this.statusHandler.clusterNodeStatus(node); diff --git a/public/controllers/management/components/management/status/status-agent-info.js b/public/controllers/management/components/management/status/status-agent-info.js index 3171d6def0..b3d8f12100 100644 --- a/public/controllers/management/components/management/status/status-agent-info.js +++ b/public/controllers/management/components/management/status/status-agent-info.js @@ -20,6 +20,8 @@ import { } from '@elastic/eui'; import { formatUIDate } from '../../../../../react-services/time-service'; import { connect } from 'react-redux'; +import { API_NAME_AGENT_STATUS } from '../../../../../../common/constants'; +import { agentStatusLabelByAgentStatus } from '../../../../../../common/services/wz_agent_status'; export class WzStatusAgentInfo extends Component { _isMounted = false; @@ -40,7 +42,7 @@ export class WzStatusAgentInfo extends Component { const { agentInfo } = this.props.state; const status = agentInfo.status; let operatingSystem = false; - if (status !== 'never_connected' && agentInfo.os) { + if (status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED && agentInfo.os) { operatingSystem = agentInfo.os.name ? agentInfo.os.name + agentInfo.os.version : agentInfo.os.uname @@ -75,7 +77,7 @@ export class WzStatusAgentInfo extends Component { Status - {agentInfo.status} + {agentStatusLabelByAgentStatus(agentInfo.status)} IP Address @@ -85,7 +87,7 @@ export class WzStatusAgentInfo extends Component { Date added {formatUIDate(agentInfo.dateAdd)} - {status !== 'never_connected' && ( + {status !== API_NAME_AGENT_STATUS.NEVER_CONNECTED && (

Version diff --git a/public/controllers/management/components/management/status/status-node-info.js b/public/controllers/management/components/management/status/status-node-info.js index 4c04733dc6..aa6dc15196 100644 --- a/public/controllers/management/components/management/status/status-node-info.js +++ b/public/controllers/management/components/management/status/status-node-info.js @@ -32,7 +32,7 @@ export class WzStatusNodeInfo extends Component { render() { const { stats, nodeInfo, selectedNode, clusterEnabled } = this.props.state; - const agentsNodeCount = clusterEnabled ? (stats.agentsCount.find(node => node.node_name === selectedNode) || {}).count || 0 : stats.agentsCountTotal; + const agentsNodeCount = clusterEnabled ? (stats.agentsCountByManagerNodes.find(node => node.node_name === selectedNode) || {}).count || 0 : stats.agentsCount.total; const title = selectedNode ? selectedNode + ' information' : 'Manager information'; diff --git a/public/controllers/management/components/management/status/status-overview.js b/public/controllers/management/components/management/status/status-overview.js index ceeed7f1ea..a2f20feb3d 100644 --- a/public/controllers/management/components/management/status/status-overview.js +++ b/public/controllers/management/components/management/status/status-overview.js @@ -93,31 +93,18 @@ export class WzStatusOverview extends Component { try { this.props.updateLoadingStatus(true); - const agSumm = await this.statusHandler.agentsSummary(); - const clusStat = await this.statusHandler.clusterStatus(); - const manInfo = await this.statusHandler.managerInfo(); - const agentsCountResponse = await this.statusHandler.clusterAgentsCount(); - const data = []; - data.push(agSumm); - data.push(clusStat); - data.push(manInfo); - data.push(agentsCountResponse); - - const parsedData = data.map((item) => ((item || {}).data || {}).data || false); - const [stats, clusterStatus, managerInfo, agentsCount] = parsedData; - - // Once Wazuh core fixes agent 000 issues, this should be adjusted - const active = stats.active; - const total = stats.total; + const [agentsCount, clusterStatus, managerInfo, agentsCountByManagerNodes] = (await Promise.all([ + this.statusHandler.agentsSummary(), + this.statusHandler.clusterStatus(), + this.statusHandler.managerInfo(), + this.statusHandler.clusterAgentsCount() + ])).map(response => response?.data?.data); this.props.updateStats({ - agentsCount: agentsCount.nodes, - agentsCountActive: active, - agentsCountDisconnected: stats.disconnected, - agentsCountNeverConnected: stats.never_connected, - agentsCountTotal: total, - agentsCoverity: total ? (active / total) * 100 : 0, + agentsCountByManagerNodes: agentsCountByManagerNodes.nodes, + agentsCount, + agentsCoverage: agentsCount.total ? ((agentsCount.active / agentsCount.total) * 100).toFixed(2) : 0, }); this.props.updateClusterEnabled(clusterStatus && clusterStatus.enabled === 'yes'); diff --git a/public/controllers/management/components/management/status/status-stats.js b/public/controllers/management/components/management/status/status-stats.js index 486cc1dc1f..ddfe719192 100644 --- a/public/controllers/management/components/management/status/status-stats.js +++ b/public/controllers/management/components/management/status/status-stats.js @@ -14,12 +14,24 @@ import React, { Component } from 'react'; import { EuiStat, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { connect } from 'react-redux'; +import { UI_ORDER_AGENT_STATUS } from '../../../../../../common/constants'; +import { agentStatusColorByAgentStatus, agentStatusLabelByAgentStatus } from '../../../../../../common/services/wz_agent_status'; export class WzStatusStats extends Component { _isMounted = false; constructor(props) { super(props); this.state = {}; + this.agentStatus = ['total', ...UI_ORDER_AGENT_STATUS].map(status => ({ + color: status !== 'total' ? agentStatusColorByAgentStatus(status) : 'primary', + description: `${status === 'total' ? 'Total agents' : agentStatusLabelByAgentStatus(status)}`, + status + })); + this.agentStatus.push({ + color: undefined, + description: 'Agents coverage', + status: 'coverage' + }) } componentDidMount() { @@ -34,52 +46,21 @@ export class WzStatusStats extends Component { render() { const { stats } = this.props.state; - const agentsCoverage = stats.agentsCoverity.toFixed(2) + '%'; return (
- - - - - - - - - - - - - - - + {this.agentStatus.map(({color, description, status}) => ( + + + + ))}
diff --git a/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap b/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap index ec89c47c2a..ac13fef1bc 100644 --- a/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap +++ b/public/controllers/overview/components/__snapshots__/stats.test.tsx.snap @@ -17,7 +17,9 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` className="euiFlexItem" /> - +
@@ -26,7 +28,7 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` textAlign="center" title={ @@ -38,7 +40,9 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` "cursor": "pointer", } } - /> + > + - + } titleColor="primary" @@ -69,7 +73,7 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` className="euiTitle euiTitle--large euiStat__title euiStat__title--primary" > @@ -89,7 +93,9 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` "cursor": "pointer", } } - /> + > + - +

@@ -98,7 +104,9 @@ exports[`Stats component renders correctly to match the snapshot 1`] = `
- +
@@ -119,10 +127,12 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` "cursor": "pointer", } } - /> + > + - + } - titleColor="secondary" + titleColor="#007871" >

+ > + - +

@@ -179,7 +196,9 @@ exports[`Stats component renders correctly to match the snapshot 1`] = `
- +
@@ -200,10 +219,12 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` "cursor": "pointer", } } - /> + > + - + } - titleColor="danger" + titleColor="#BD271E" >

+ > + - +

@@ -260,7 +288,101 @@ exports[`Stats component renders correctly to match the snapshot 1`] = `
- + +
+ + + - + + + } + titleColor="#FEC514" + > +
+ +
+

+ Pending agents +

+
+
+ +

+ + + + - + + + +

+
+
+
+
+
+
@@ -281,10 +403,12 @@ exports[`Stats component renders correctly to match the snapshot 1`] = ` "cursor": "pointer", } } - /> + > + - + } - titleColor="subdued" + titleColor="#646A77" >

+ > + - +

diff --git a/public/controllers/overview/components/overview-actions/agents-selection-table.js b/public/controllers/overview/components/overview-actions/agents-selection-table.js index 9a7515aba0..759ec023de 100644 --- a/public/controllers/overview/components/overview-actions/agents-selection-table.js +++ b/public/controllers/overview/components/overview-actions/agents-selection-table.js @@ -28,9 +28,10 @@ import { GroupTruncate } from '../../../../components/common/util/agent-group-tr import { filtersToObject, WzSearchBar } from '../../../../components/wz-search-bar'; import { getAgentFilterValues } from '../../../../controllers/management/components/management/groups/get-agents-filters-values'; import _ from 'lodash'; -import { UI_LOGGER_LEVELS } from '../../../../../common/constants'; +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 { AgentStatus } from '../../../../components/agents/agent_status'; const checkField = field => { return field !== undefined ? field : '-'; @@ -113,11 +114,11 @@ export class AgentSelectionTable extends Component { }, isSortable: true, width: 'auto', - render: status => this.addHealthStatusRender(status), + render: status => , }, ]; this.suggestions = [ - { type: 'q', label: 'status', description: 'Filter by agent connection status', operators: ['=', '!=',], values: ['active', 'disconnected', 'never_connected', 'pending'] }, + { type: 'q', label: 'status', description: 'Filter by agent connection status', operators: ['=', '!=',], values: UI_ORDER_AGENT_STATUS }, { type: 'q', label: 'os.platform', description: 'Filter by OS platform', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('os.platform', value, { q: 'id!=000'})}, { type: 'q', label: 'ip', description: 'Filter by agent IP', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('ip', value, { q: 'id!=000'})}, { type: 'q', label: 'name', description: 'Filter by agent name', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('name', value, { q: 'id!=000'})}, @@ -217,24 +218,6 @@ export class AgentSelectionTable extends Component { } } - agentStatusColor(status){ - if (status.toLowerCase() === 'active') { - return 'success'; - } else if (status.toLowerCase() === 'disconnected') { - return 'danger'; - } else if (status.toLowerCase() === 'never_connected') { - return 'subdued'; - } - } - - addHealthStatusRender(status) { - return ( - - {status === 'never_connected' ? 'never connected' : status} - - ); - } - buildFilter() { const { itemsPerPage, pageIndex, filters } = this.state; const filter = { @@ -650,9 +633,9 @@ export class AgentSelectionTable extends Component { {/* agent name (agent id) Unpin button right aligned, require justifyContent="flexEnd" in the EuiFlexGroup */} - + {selectedAgent.name} ({selectedAgent.id}) - + diff --git a/public/controllers/overview/components/stats.js b/public/controllers/overview/components/stats.js index cab4dc7622..a9b1786250 100644 --- a/public/controllers/overview/components/stats.js +++ b/public/controllers/overview/components/stats.js @@ -14,12 +14,20 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { EuiStat, EuiFlexItem, EuiFlexGroup, EuiPage, EuiToolTip } from '@elastic/eui'; import { withErrorBoundary } from '../../../components/common/hocs'; +import { UI_ORDER_AGENT_STATUS } from '../../../../common/constants'; +import { agentStatusLabelByAgentStatus, agentStatusColorByAgentStatus } from '../../../../common/services/wz_agent_status'; export const Stats = withErrorBoundary (class Stats extends Component { constructor(props) { super(props); this.state = {}; + this.agentStatus = ['total', ...UI_ORDER_AGENT_STATUS].map(status => ({ + status, + label: status !== 'total' ? agentStatusLabelByAgentStatus(status) : 'Total', + onClick: () => this.goToAgents(status !== 'total' ? status : null), + color: status !== 'total' ? agentStatusColorByAgentStatus(status) : 'primary' + })); } goToAgents(status) { @@ -47,78 +55,26 @@ export const Stats = withErrorBoundary (class Stats extends Component { - - - this.goToAgents(null)} - > - {this.props.total} - - - } - description="Total agents" - titleColor="primary" - textAlign="center" - /> - - - - this.goToAgents('active')} - className={ 'statWithLink' } - style={{ cursor: "pointer" }} - > - {this.props.active} - - - } - description="Active agents" - titleColor="secondary" - textAlign="center" - /> - - - - this.goToAgents('disconnected')} - className={ 'statWithLink' } - style={{ cursor: "pointer" }} - > - {this.props.disconnected} - - - } - description="Disconnected agents" - titleColor="danger" - textAlign="center" - /> - - - - this.goToAgents('never_connected')} - className={ 'statWithLink' } - style={{ cursor: "pointer" }} - > - {this.props.neverConnected} - - - } - description="Never connected agents" - titleColor="subdued" - textAlign="center" - /> - + {this.agentStatus.map(({status, label, onClick, color}) => ( + + + + {typeof this.props[status] !== 'undefined' ? this.props[status] : '-'} + + + } + description={`${label} agents`} + titleColor={color} + textAlign="center" + /> + + ))} @@ -130,5 +86,6 @@ Stats.propTypes = { total: PropTypes.any, active: PropTypes.any, disconnected: PropTypes.any, - neverConnected: PropTypes.any + pending: PropTypes.any, + never_connected: PropTypes.any }; diff --git a/public/controllers/overview/overview.js b/public/controllers/overview/overview.js index 682c3ed4f4..abd4dc235e 100644 --- a/public/controllers/overview/overview.js +++ b/public/controllers/overview/overview.js @@ -282,7 +282,7 @@ export class OverviewController { timefilter.setRefreshInterval(this.commonData.getRefreshInterval()); } - if (typeof this.agentsCountTotal === 'undefined') { + if (typeof this.agentsCount === 'undefined') { await this.getSummary(); } @@ -329,22 +329,10 @@ export class OverviewController { */ async getSummary() { try { - const data = await WzRequest.apiReq('GET', '/agents/summary/status', {}); - - const result = ((data || {}).data || {}).data || false; - - if (result) { - const active = result.active; - const total = result.total; - this.agentsCountActive = active; - this.agentsCountDisconnected = result.disconnected; - this.agentsCountNeverConnected = result['never_connected']; - this.agentsCountTotal = total; - this.welcomeCardsProps.agentsCountTotal = total; - this.agentsCoverity = total ? (active / total) * 100 : 0; - } else { - throw new Error('Error fetching /agents/summary from Wazuh API'); - } + const {data: { data }} = await WzRequest.apiReq('GET', '/agents/summary/status', {}); + this.agentsCount = data; + this.welcomeCardsProps.agentsCountTotal = data.total; + this.agentsCoverity = data.total ? (data.active / data.total) * 100 : 0; } catch (error) { return Promise.reject(error); } diff --git a/public/kibana-integrations/kibana-vis.js b/public/kibana-integrations/kibana-vis.js index 2364e60a7b..e07a53f911 100644 --- a/public/kibana-integrations/kibana-vis.js +++ b/public/kibana-integrations/kibana-vis.js @@ -25,6 +25,7 @@ import { updateMetric } from '../redux/actions/visualizationsActions'; import { GenericRequest } from '../react-services/generic-request'; import { createSavedVisLoader } from './visualizations/saved_visualizations'; import { WzDatePicker } from '../components/wz-date-picker/wz-date-picker'; +import { Vis } from '../../../../src/plugins/visualizations/public'; import { EuiLoadingChart, EuiLoadingSpinner, @@ -282,10 +283,16 @@ class KibanaVis extends Component { const visState = await getVisualizationsPlugin().convertToSerializedVis( this.visualization ); - const vis = await getVisualizationsPlugin().createVis( - this.visualization.visState.type, - visState - ); + + // In Kibana 7.10.2, there is a bug when creating the visualization with `createVis` method of the Visualization plugin that doesn't pass the `visState` parameter to the `Vis` class constructor. + // This does the `.params`, `.uiState` and `.id` properties of the visualization are not set correctly in the `Vis` class constructor. This bug causes + // that the visualization, for example, doesn't use the defined colors in the `.uiStateJSON` property. + // `createVis` method of Visualizations plugin: https://github.com/elastic/kibana/blob/v7.10.2/src/plugins/visualizations/public/plugin.ts#L207-L211 + // `Vis` class constructor: https://github.com/elastic/kibana/blob/v7.10.2/src/plugins/visualizations/public/vis.ts#L99-L104 + // This problem is fixed replicating the logic of Visualization plugin's `createVis` method and pass the expected parameters to the `Vis` class constructor. + const vis = new Vis(visState.type, visState); + await vis.setState(visState); + this.visHandler = await getVisualizationsPlugin().__LEGACY.createVisEmbeddableFromObject( vis, visInput diff --git a/public/react-services/action-agents.js b/public/react-services/action-agents.js index 99f40e6606..361e176054 100644 --- a/public/react-services/action-agents.js +++ b/public/react-services/action-agents.js @@ -11,7 +11,7 @@ */ import { WzRequest } from './wz-request'; import { getToasts } from '../kibana-services'; -import { UI_LOGGER_LEVELS } from '../../common/constants'; +import { API_NAME_AGENT_STATUS, UI_LOGGER_LEVELS } from '../../common/constants'; import { UI_ERROR_SEVERITIES } from './error-orchestrator/types'; import { getErrorOrchestrator } from './common-services'; @@ -57,7 +57,7 @@ export class ActionAgents { */ static async upgradeAgents(selectedItems) { for (let item of selectedItems.filter( - (item) => item.outdated && item.status !== 'Disconnected' + (item) => item.outdated && item.status !== API_NAME_AGENT_STATUS.DISCONNECTED )) { try { await WzRequest.apiReq('PUT', `/agents/${item.id}/upgrade`, '1'); @@ -89,7 +89,7 @@ export class ActionAgents { if ( agent.id !== '000' && this.compareVersions(agent.version, managerVersion) === true && - agent.status === 'active' + agent.status === API_NAME_AGENT_STATUS.ACTIVE ) { try { await WzRequest.apiReq('PUT', `/agents/${agent.id}/upgrade`, '1'); @@ -168,7 +168,7 @@ export class ActionAgents { static async restartAllAgents(selectedItems) { let idAvaibleAgents = []; selectedItems.forEach((agent) => { - if (agent.id !== '000' && agent.status === 'active') { + if (agent.id !== '000' && agent.status === API_NAME_AGENT_STATUS.ACTIVE) { idAvaibleAgents.push(agent.id); } }); diff --git a/public/templates/visualize/dashboards.html b/public/templates/visualize/dashboards.html index 567b34f208..f03852120a 100644 --- a/public/templates/visualize/dashboards.html +++ b/public/templates/visualize/dashboards.html @@ -108,11 +108,7 @@ name="StatsOverview" style="padding-bottom: 0" flex - props="{ - total: octrl.agentsCountTotal >= 0 ? octrl.agentsCountTotal : '-', - active: octrl.agentsCountActive >= 0 ? octrl.agentsCountActive : '-', - disconnected: octrl.agentsCountDisconnected >= 0 ? octrl.agentsCountDisconnected : '-', - neverConnected: octrl.agentsCountNeverConnected >= 0 ? octrl.agentsCountNeverConnected : '-'}" + props="octrl.agentsCount" />
diff --git a/public/templates/visualize/overview-welcome.html b/public/templates/visualize/overview-welcome.html index 675fe37725..906b5f0143 100644 --- a/public/templates/visualize/overview-welcome.html +++ b/public/templates/visualize/overview-welcome.html @@ -1,10 +1,6 @@
- +
\ No newline at end of file diff --git a/server/controllers/wazuh-reporting.ts b/server/controllers/wazuh-reporting.ts index a5731a4029..0b4379b0c7 100644 --- a/server/controllers/wazuh-reporting.ts +++ b/server/controllers/wazuh-reporting.ts @@ -36,9 +36,11 @@ import { WAZUH_DATA_DOWNLOADS_DIRECTORY_PATH, WAZUH_DATA_DOWNLOADS_REPORTS_DIRECTORY_PATH, AUTHORIZED_AGENTS, + API_NAME_AGENT_STATUS, } from '../../common/constants'; import { createDirectoryIfNotExists, createDataDirectoryIfNotExists } from '../lib/filesystem'; import moment from 'moment'; +import { agentStatusLabelByAgentStatus } from '../../common/services/wz_agent_status'; export class WazuhReportingCtrl { constructor() {} @@ -164,9 +166,9 @@ export class WazuhReportingCtrl { { apiHostID: apiId } ); const agentData = agentResponse.data.data.affected_items[0]; - if (agentData && agentData.status !== 'active') { + if (agentData && agentData.status !== API_NAME_AGENT_STATUS.ACTIVE) { printer.addContentWithNewLine({ - text: `Warning. Agent is ${agentData.status.toLowerCase()}`, + text: `Warning. Agent is ${agentStatusLabelByAgentStatus(agentData.status).toLowerCase()}`, style: 'standard', }); } diff --git a/server/integration-files/visualizations/overview/overview-general.ts b/server/integration-files/visualizations/overview/overview-general.ts index 3bd4ae7057..3dc8cc759a 100644 --- a/server/integration-files/visualizations/overview/overview-general.ts +++ b/server/integration-files/visualizations/overview/overview-general.ts @@ -9,6 +9,8 @@ * * Find more information about this on the LICENSE file. */ +import { UI_COLOR_AGENT_STATUS } from "../../../../common/constants"; + export default [ { _id: 'Wazuh-App-Overview-General-Agents-status', @@ -96,7 +98,7 @@ export default [ ], }), uiStateJSON: JSON.stringify({ - vis: { colors: { never_connected: '#447EBC', active: '#E5AC0E' } }, + vis: { colors: { active: UI_COLOR_AGENT_STATUS.active, disconnected: UI_COLOR_AGENT_STATUS.disconnected, pending: UI_COLOR_AGENT_STATUS.pending, never_connected: UI_COLOR_AGENT_STATUS.never_connected } }, }), description: '', version: 1, diff --git a/server/integration-files/visualizations/overview/overview-office.ts b/server/integration-files/visualizations/overview/overview-office.ts index f0d4980753..902b76128a 100644 --- a/server/integration-files/visualizations/overview/overview-office.ts +++ b/server/integration-files/visualizations/overview/overview-office.ts @@ -9,6 +9,8 @@ * * Find more information about this on the LICENSE file. */ +import { UI_COLOR_AGENT_STATUS } from "../../../../common/constants"; + export default [ { _id: 'Wazuh-App-Overview-Office-Agents-status', @@ -96,7 +98,7 @@ export default [ ], }), uiStateJSON: JSON.stringify({ - vis: { colors: { never_connected: '#447EBC', active: '#E5AC0E' } }, + vis: { colors: { active: UI_COLOR_AGENT_STATUS.active, disconnected: UI_COLOR_AGENT_STATUS.disconnected, pending: UI_COLOR_AGENT_STATUS.pending, never_connected: UI_COLOR_AGENT_STATUS.never_connected } }, }), description: '', version: 1, From d9f7c3d2b861d81959478579499203542a316e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Wed, 25 May 2022 15:41:58 +0200 Subject: [PATCH 2/4] changelog: Add PR entry --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91cef8e754..5ad7ed905d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to the Wazuh app project will be documented in this file. +## Wazuh v4.3.2 - Kibana 7.10.2, 7.16.x, 7.17.x - Revision 4303 + +### Added + +- Added the `pending` agent status to some sections that was missing [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166) + +### Changed + +- Replaced the visualization of `Status` panel in `Agents` [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166) +- Replaced the visualization of policy in `Modules/Security configuration assessment/Invetory` [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166) +- Consistency in the colors and labels used for the agent status [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166) + +### Fixed +- Fixed the platform visualizations didn't use some definitions related to the UI on Kibana 7.10.2 [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166) + ## Wazuh v4.3.1 - Kibana 7.10.2, 7.16.x, 7.17.x - Revision 4302 ### Added From 0e77fecaff4e396062e23b13bc8714c3da1ee69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex?= Date: Wed, 1 Jun 2022 13:21:18 +0200 Subject: [PATCH 3/4] Update CHANGELOG.md Add missing parentheses --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d9cb49170..4aad693717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Consistency in the colors and labels used for the agent status [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166) ### Fixed -- Fixed that the platform visualizations didn't use some definitions related to the UI on Kibana 7.10.2 [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166 +- Fixed that the platform visualizations didn't use some definitions related to the UI on Kibana 7.10.2 [#4166](https://github.com/wazuh/wazuh-kibana-app/pull/4166) - Fixed Wazuh Dashboard troubleshooting url [#4150](https://github.com/wazuh/wazuh-kibana-app/pull/4150) ## Wazuh v4.3.1 - Kibana 7.10.2, 7.16.x, 7.17.x - Revision 4302 From caa96b8ab61f739e709cac49d7d02dc641a2582d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20David=20Guti=C3=A9rrez?= Date: Thu, 2 Jun 2022 11:43:06 +0200 Subject: [PATCH 4/4] fix: replaced hardcoded values for a type related to the agent status --- common/constants.ts | 6 +++--- common/services/wz_agent_status.ts | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index c81eaee4bb..79a86d93d3 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -372,7 +372,7 @@ export const API_NAME_AGENT_STATUS = { DISCONNECTED: 'disconnected', PENDING: 'pending', NEVER_CONNECTED: 'never_connected', -} +} as const; export const UI_COLOR_AGENT_STATUS = { [API_NAME_AGENT_STATUS.ACTIVE]: '#007871', @@ -380,7 +380,7 @@ export const UI_COLOR_AGENT_STATUS = { [API_NAME_AGENT_STATUS.PENDING]: '#FEC514', [API_NAME_AGENT_STATUS.NEVER_CONNECTED]: '#646A77', default: '#000000' -} +} as const; export const UI_LABEL_NAME_AGENT_STATUS = { [API_NAME_AGENT_STATUS.ACTIVE]: 'Active', @@ -388,7 +388,7 @@ export const UI_LABEL_NAME_AGENT_STATUS = { [API_NAME_AGENT_STATUS.PENDING]: 'Pending', [API_NAME_AGENT_STATUS.NEVER_CONNECTED]: 'Never connected', default: 'Unknown' -} +} as const export const UI_ORDER_AGENT_STATUS = [ API_NAME_AGENT_STATUS.ACTIVE, diff --git a/common/services/wz_agent_status.ts b/common/services/wz_agent_status.ts index dbbd6602ac..3c37e98a92 100644 --- a/common/services/wz_agent_status.ts +++ b/common/services/wz_agent_status.ts @@ -1,11 +1,14 @@ -import { UI_COLOR_AGENT_STATUS, UI_LABEL_NAME_AGENT_STATUS } from '../constants'; +import { UI_COLOR_AGENT_STATUS, UI_LABEL_NAME_AGENT_STATUS, API_NAME_AGENT_STATUS } from '../constants'; -type AgentStatus = 'active' | 'disconected' | 'pending' | 'never_connected'; +type TAgentStatus = typeof API_NAME_AGENT_STATUS[keyof typeof API_NAME_AGENT_STATUS]; -export function agentStatusColorByAgentStatus(status: AgentStatus): string{ +type TAgentStatusColor = typeof UI_COLOR_AGENT_STATUS[keyof typeof UI_COLOR_AGENT_STATUS]; +type TAgentStatusLabel = typeof UI_LABEL_NAME_AGENT_STATUS[keyof typeof UI_LABEL_NAME_AGENT_STATUS]; + +export function agentStatusColorByAgentStatus(status: TAgentStatus): TAgentStatusColor{ return UI_COLOR_AGENT_STATUS[status] || UI_COLOR_AGENT_STATUS.default; } -export function agentStatusLabelByAgentStatus(status: AgentStatus): string{ +export function agentStatusLabelByAgentStatus(status: TAgentStatus): TAgentStatusLabel{ return UI_LABEL_NAME_AGENT_STATUS[status] || UI_LABEL_NAME_AGENT_STATUS.default; } \ No newline at end of file