diff --git a/CHANGELOG.md b/CHANGELOG.md index 1baa655b08..e235a66453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Changed the query to search for an agent in `management/configuration`. [#5485](https://github.com/wazuh/wazuh-kibana-app/pull/5485) - Changed the search bar in management/log to the one used in the rest of the app. [#5476](https://github.com/wazuh/wazuh-kibana-app/pull/5476) - Changed the design of the wizard to add agents. [#5457](https://github.com/wazuh/wazuh-kibana-app/pull/5457) -- Changed the search bar in Management (Rules, Decoders, CDB List, Groups) and Modules (Vulnerabilities > Inventory, Security Configuration Assessment > Inventory > {Policy ID} > Checks, MITRE ATT&CK > Intelligence > {Resource}, Integrity monitoring > Inventory > Files, Integrity monitoring > Inventory > Registry), Agent Inventory data [#5363](https://github.com/wazuh/wazuh-kibana-app/pull/5363) [#5442](https://github.com/wazuh/wazuh-kibana-app/pull/5442) [#5443](https://github.com/wazuh/wazuh-kibana-app/pull/5443) [#5444](https://github.com/wazuh/wazuh-kibana-app/pull/5444) [#5445](https://github.com/wazuh/wazuh-kibana-app/pull/5445) +- Changed the search bar in Management (Rules, Decoders, CDB List, Groups) and Modules (Vulnerabilities > Inventory, Security Configuration Assessment > Inventory > {Policy ID} > Checks, MITRE ATT&CK > Intelligence > {Resource}, Integrity monitoring > Inventory > Files, Integrity monitoring > Inventory > Registry), Agent Inventory data, Explore agent modal [#5363](https://github.com/wazuh/wazuh-kibana-app/pull/5363) [#5442](https://github.com/wazuh/wazuh-kibana-app/pull/5442) [#5443](https://github.com/wazuh/wazuh-kibana-app/pull/5443) [#5444](https://github.com/wazuh/wazuh-kibana-app/pull/5444) [#5445](https://github.com/wazuh/wazuh-kibana-app/pull/5445) [#5447](https://github.com/wazuh/wazuh-kibana-app/pull/5447) ### Fixed diff --git a/plugins/main/public/components/common/tables/table-with-search-bar.tsx b/plugins/main/public/components/common/tables/table-with-search-bar.tsx index d8f0df3e7a..1f60167d3a 100644 --- a/plugins/main/public/components/common/tables/table-with-search-bar.tsx +++ b/plugins/main/public/components/common/tables/table-with-search-bar.tsx @@ -18,68 +18,80 @@ import { UI_LOGGER_LEVELS } from '../../../../common/constants'; import { getErrorOrchestrator } from '../../../react-services/common-services'; import { SearchBar, SearchBarProps } from '../../search-bar'; -export interface ITableWithSearcHBarProps{ +export interface ITableWithSearcHBarProps { /** * Function to fetch the data */ onSearch: ( endpoint: string, filters: Record, - pagination: {pageIndex: number, pageSize: number}, - sorting: {sort: {field: string, direction: string}} - ) => Promise<{items: any[], totalItems: number}> + pagination: { pageIndex: number; pageSize: number }, + sorting: { sort: { field: string; direction: string } }, + ) => Promise<{ items: any[]; totalItems: number }>; /** * Properties for the search bar */ - searchBarProps?: Omit + searchBarProps?: Omit< + SearchBarProps, + 'defaultMode' | 'modes' | 'onSearch' | 'input' + >; /** * Columns for the table */ tableColumns: EuiBasicTableProps['columns'] & { - composeField?: string[], - searchable?: string - show?: boolean, - } + composeField?: string[]; + searchable?: string; + show?: boolean; + }; /** * Table row properties for the table */ - rowProps?: EuiBasicTableProps['rowProps'] + rowProps?: EuiBasicTableProps['rowProps']; /** * Table page size options */ - tablePageSizeOptions?: number[] + tablePageSizeOptions?: number[]; /** * Table initial sorting direction */ - tableInitialSortingDirection?: 'asc' | 'dsc' + tableInitialSortingDirection?: 'asc' | 'desc'; /** * Table initial sorting field */ - tableInitialSortingField?: string + tableInitialSortingField?: string; /** * Table properties */ - tableProps?: Omit, 'columns' | 'items' | 'loading' | 'pagination' | 'sorting' | 'onChange' | 'rowProps'> + tableProps?: Omit< + EuiBasicTableProps, + | 'columns' + | 'items' + | 'loading' + | 'pagination' + | 'sorting' + | 'onChange' + | 'rowProps' + >; /** * Refresh the fetch of data */ - reload?: number + reload?: number; /** * API endpoint */ - endpoint: string + endpoint: string; /** * Search bar properties for WQL */ - searchBarWQL?: any + searchBarWQL?: any; /** * Visible fields */ - selectedFields: string[] + selectedFields: string[]; /** * API request filters */ - filters?: any + filters?: any; } export function TableWithSearchBar({ @@ -113,18 +125,24 @@ export function TableWithSearchBar({ const isMounted = useRef(false); - const searchBarWQLOptions = useMemo(() => ({ - searchTermFields: tableColumns - .filter(({field, searchable}) => searchable && rest.selectedFields.includes(field)) - .map(({field, composeField}) => ([composeField || field].flat())) - .flat(), - ...(rest?.searchBarWQL?.options || {}) - }), [rest?.searchBarWQL?.options, rest?.selectedFields]); + const searchBarWQLOptions = useMemo( + () => ({ + searchTermFields: tableColumns + .filter( + ({ field, searchable }) => + searchable && rest.selectedFields.includes(field), + ) + .map(({ field, composeField }) => [composeField || field].flat()) + .flat(), + ...(rest?.searchBarWQL?.options || {}), + }), + [rest?.searchBarWQL?.options, rest?.selectedFields], + ); function updateRefresh() { setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); setRefresh(Date.now()); - }; + } function tableOnChange({ page = {}, sort = {} }) { if (isMounted.current) { @@ -154,31 +172,39 @@ export function TableWithSearchBar({ } }, [endpoint, reload]); - useEffect(function () { - (async () => { - try { - setLoading(true); - const { items, totalItems } = await onSearch(endpoint, filters, pagination, sorting); - setItems(items); - setTotalItems(totalItems); - } catch (error) { - setItems([]); - setTotalItems(0); - const options = { - context: `${TableWithSearchBar.name}.useEffect`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: `${error.name}: Error fetching items`, - }, - }; - getErrorOrchestrator().handleError(options); - } - setLoading(false); - })(); - }, [filters, pagination, sorting, refresh]); + useEffect( + function () { + (async () => { + try { + setLoading(true); + const { items, totalItems } = await onSearch( + endpoint, + filters, + pagination, + sorting, + ); + setItems(items); + setTotalItems(totalItems); + } catch (error) { + setItems([]); + setTotalItems(0); + const options = { + context: `${TableWithSearchBar.name}.useEffect`, + level: UI_LOGGER_LEVELS.ERROR, + severity: UI_ERROR_SEVERITIES.BUSINESS, + error: { + error: error, + message: error.message || error, + title: `${error.name}: Error fetching items`, + }, + }; + getErrorOrchestrator().handleError(options); + } + setLoading(false); + })(); + }, + [filters, pagination, sorting, refresh], + ); useEffect(() => { // This effect is triggered when the component is mounted because of how to the useEffect hook works. @@ -211,20 +237,26 @@ export function TableWithSearchBar({ { id: 'wql', options: searchBarWQLOptions, - ...( rest?.searchBarWQL?.suggestions ? {suggestions: rest.searchBarWQL.suggestions} : {}), - ...( rest?.searchBarWQL?.validate ? {validate: rest.searchBarWQL.validate} : {}) - } + ...(rest?.searchBarWQL?.suggestions + ? { suggestions: rest.searchBarWQL.suggestions } + : {}), + ...(rest?.searchBarWQL?.validate + ? { validate: rest.searchBarWQL.validate } + : {}), + }, ]} input={rest?.filters?.q || ''} - onSearch={({apiQuery}) => { + onSearch={({ apiQuery }) => { // Set the query, reset the page index and update the refresh setFilters(apiQuery); updateRefresh(); }} /> - + ({...rest}))} + columns={tableColumns.map( + ({ searchable, show, composeField, ...rest }) => ({ ...rest }), + )} items={items} loading={loading} pagination={tablePagination} diff --git a/plugins/main/public/controllers/overview/components/overview-actions/agents-selection-table.js b/plugins/main/public/controllers/overview/components/overview-actions/agents-selection-table.js index 99d6277b13..99cc5a3790 100644 --- a/plugins/main/public/controllers/overview/components/overview-actions/agents-selection-table.js +++ b/plugins/main/public/controllers/overview/components/overview-actions/agents-selection-table.js @@ -1,534 +1,103 @@ import React, { Component, Fragment } from 'react'; import { EuiButtonIcon, - EuiCheckbox, EuiFlexGroup, EuiFlexItem, - EuiHealth, EuiSpacer, - EuiTable, - EuiTableBody, - EuiTableFooterCell, - EuiTableHeader, - EuiTableHeaderCell, - EuiTableHeaderCellCheckbox, - EuiTableHeaderMobile, - EuiTablePagination, - EuiTableRow, - EuiTableRowCell, - EuiTableRowCellCheckbox, - EuiTableSortMobile, EuiToolTip, } from '@elastic/eui'; import { WzRequest } from '../../../../react-services/wz-request'; -import { LEFT_ALIGNMENT } from '@elastic/eui/lib/services'; import { updateCurrentAgentData } from '../../../../redux/actions/appStateActions'; import store from '../../../../redux/store'; import { GroupTruncate } from '../../../../components/common/util/agent-group-truncate/'; -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, UI_ORDER_AGENT_STATUS } from '../../../../../common/constants'; +import { get as getLodash } from 'lodash'; +import { + SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + 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'; +import { TableWzAPI } from '../../../../components/common/tables'; -const checkField = field => { - return field !== undefined ? field : '-'; +const searchBarWQLOptions = { + implicitQuery: { + query: 'id!=000', + conjunction: ';', + }, }; export class AgentSelectionTable extends Component { constructor(props) { super(props); this.state = { - itemIdToSelectedMap: {}, - itemIdToOpenActionsPopoverMap: {}, - sortedColumn: 'title', - itemsPerPage: 10, - pageIndex: 0, - totalItems: 0, - isLoading: false, - sortDirection: 'asc', - sortField: 'id', - agents: [], - selectedOptions: [], - filters: [] + filters: { default: { q: 'id!=000' } }, }; this.columns = [ { - id: 'id', - label: 'ID', - alignment: LEFT_ALIGNMENT, + field: 'id', + name: 'ID', width: '60px', - mobileOptions: { - show: true, - }, - isSortable: true, + searchable: true, + sortable: true, }, { - id: 'name', - label: 'Name', - alignment: LEFT_ALIGNMENT, - mobileOptions: { - show: true, - }, - isSortable: true + field: 'name', + name: 'Name', + searchable: true, + sortable: true, }, { - id: 'group', - label: 'Group', - alignment: LEFT_ALIGNMENT, - mobileOptions: { - show: false, - }, - isSortable: true, - render: groups => this.renderGroups(groups) + field: 'group', + name: 'Group', + sortable: true, + searchable: true, + render: groups => this.renderGroups(groups), }, { - id: 'version', - label: 'Version', + field: 'version', + name: 'Version', width: '80px', - alignment: LEFT_ALIGNMENT, - mobileOptions: { - show: true, - }, - isSortable: true, + searchable: true, + sortable: true, }, { - id: 'os', - label: 'Operating system', - alignment: LEFT_ALIGNMENT, - mobileOptions: { - show: false, - }, - isSortable: true, - render: os => this.addIconPlatformRender(os) + field: 'os.name,os.version', + composeField: ['os.name', 'os.version'], + name: 'Operating system', + sortable: true, + searchable: true, + render: (field, agentData) => this.addIconPlatformRender(agentData), }, { - id: 'status', - label: 'Status', - alignment: LEFT_ALIGNMENT, - mobileOptions: { - show: true, - }, - isSortable: true, + field: 'status', + name: 'Status', + searchable: true, + sortable: true, width: 'auto', - render: status => , + render: status => ( + + ), }, ]; - this.suggestions = [ - { type: 'q', label: 'status', description: 'Filter by agent connection status', operators: ['=', '!=',], values: UI_ORDER_AGENT_STATUS }, - { type: 'q', label: 'os.platform', description: 'Filter by operating system platform', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('os.platform', value, { q: 'id!=000'})}, - { type: 'q', label: 'ip', description: 'Filter by agent IP address', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('ip', value, { q: 'id!=000'})}, - { type: 'q', label: 'name', description: 'Filter by agent name', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('name', value, { q: 'id!=000'})}, - { type: 'q', label: 'id', description: 'Filter by agent id', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('id', value, { q: 'id!=000'})}, - { type: 'q', label: 'group', description: 'Filter by agent group', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('group', value, { q: 'id!=000'})}, - { type: 'q', label: 'node_name', description: 'Filter by node name', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('node_name', value, { q: 'id!=000'})}, - { type: 'q', label: 'manager', description: 'Filter by manager', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('manager', value, { q: 'id!=000'})}, - { type: 'q', label: 'version', description: 'Filter by agent version', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('version', value, { q: 'id!=000'})}, - { type: 'q', label: 'configSum', description: 'Filter by agent config sum', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('configSum', value, { q: 'id!=000'})}, - { type: 'q', label: 'mergedSum', description: 'Filter by agent merged sum', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('mergedSum', value, { q: 'id!=000'})}, - { type: 'q', label: 'dateAdd', description: 'Filter by add date', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('dateAdd', value, { q: 'id!=000'})}, - { type: 'q', label: 'lastKeepAlive', description: 'Filter by last keep alive', operators: ['=', '!=',], values: async (value) => getAgentFilterValues('lastKeepAlive', value, { q: 'id!=000'})}, - ]; - } - - onChangeItemsPerPage = async itemsPerPage => { - this._isMounted && this.setState({ itemsPerPage }, async () => await this.getItems()); - }; - - onChangePage = async pageIndex => { - this._isMounted && this.setState({ pageIndex }, async () => await this.getItems()); - }; - - async componentDidMount() { - this._isMounted = true; - const tmpSelectedAgents = {}; - if(!store.getState().appStateReducers.currentAgentData.id){ - tmpSelectedAgents[store.getState().appStateReducers.currentAgentData.id] = true; - } - this._isMounted && this.setState({itemIdToSelectedMap: this.props.selectedAgents}); - await this.getItems(); - } - - componentWillUnmount(){ - this._isMounted = false; - } - - async componentDidUpdate(prevProps, prevState) { - if(!(_.isEqual(prevState.filters,this.state.filters))){ - await this.getItems(); - } - } - - getArrayFormatted(arrayText) { - try { - const stringText = arrayText.toString(); - const splitString = stringText.split(','); - return splitString.join(', '); - } catch (error) { - const options = { - context: `${AgentSelectionTable.name}.getArrayFormatted`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.UI, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - - getErrorOrchestrator().handleError(options); - return arrayText; - } - } - - async getItems() { - try { - this._isMounted && this.setState({ isLoading: true }); - const rawData = await WzRequest.apiReq('GET', '/agents', { params: this.buildFilter() }); - const data = (((rawData || {}).data || {}).data || {}).affected_items; - const totalItems = (((rawData || {}).data || {}).data || {}).total_affected_items; - const formattedData = data.map((item, id) => { - return { - id: item.id, - name: item.name, - version: item.version !== undefined ? item.version.split(' ')[1] : '-', - os: item.os || '-', - status: item.status, - group: item.group || '-', - }; - }); - this._isMounted && this.setState({ agents: formattedData, totalItems, isLoading: false }); - } catch (error) { - this._isMounted && this.setState({ isLoading: false }); - const options = { - context: `${AgentSelectionTable.name}.getItems`, - level: UI_LOGGER_LEVELS.ERROR, - severity: UI_ERROR_SEVERITIES.BUSINESS, - error: { - error: error, - message: error.message || error, - title: error.name || error, - }, - }; - - getErrorOrchestrator().handleError(options); - } - } - - buildFilter() { - const { itemsPerPage, pageIndex, filters } = this.state; - const filter = { - ...filtersToObject(filters), - offset: (pageIndex * itemsPerPage) || 0, - limit: pageIndex * itemsPerPage + itemsPerPage, - ...this.buildSortFilter() - }; - filter.q = !filter.q ? `id!=000` : `id!=000;${filter.q}`; - return filter; - } - - buildSortFilter() { - const { sortDirection, sortField } = this.state; - const sortFilter = {}; - if (sortField) { - const direction = sortDirection === 'asc' ? '+' : '-'; - sortFilter['sort'] = direction + (sortField === 'os'? 'os.name,os.version' : sortField); - } - - return sortFilter; - } - - onSort = async prop => { - const sortField = prop; - const sortDirection = - this.state.sortField === prop && this.state.sortDirection === 'asc' - ? 'desc' - : this.state.sortDirection === 'asc' - ? 'desc' - : 'asc'; - - this._isMounted && this.setState({ sortField, sortDirection }, async () => await this.getItems()); - }; - - toggleItem = itemId => { - this._isMounted && this.setState(previousState => { - const newItemIdToSelectedMap = { - [itemId]: !previousState.itemIdToSelectedMap[itemId], - }; - - return { - itemIdToSelectedMap: newItemIdToSelectedMap, - }; - }); - }; - - toggleAll = () => { - const allSelected = this.areAllItemsSelected(); - const newItemIdToSelectedMap = {}; - this.state.agents.forEach(item => (newItemIdToSelectedMap[item.id] = !allSelected)); - this._isMounted && this.setState({ - itemIdToSelectedMap: newItemIdToSelectedMap, - }); - }; - - isItemSelected = itemId => { - return this.state.itemIdToSelectedMap[itemId]; - }; - - areAllItemsSelected = () => { - const indexOfUnselectedItem = this.state.agents.findIndex(item => !this.isItemSelected(item.id)); - return indexOfUnselectedItem === -1; - }; - - areAnyRowsSelected = () => { - return ( - Object.keys(this.state.itemIdToSelectedMap).findIndex(id => { - return this.state.itemIdToSelectedMap[id]; - }) !== -1 - ); - }; - - togglePopover = itemId => { - this._isMounted && this.setState(previousState => { - const newItemIdToOpenActionsPopoverMap = { - ...previousState.itemIdToOpenActionsPopoverMap, - [itemId]: !previousState.itemIdToOpenActionsPopoverMap[itemId], - }; - - return { - itemIdToOpenActionsPopoverMap: newItemIdToOpenActionsPopoverMap, - }; - }); - }; - - closePopover = itemId => { - // only update the state if this item's popover is open - if (this.isPopoverOpen(itemId)) { - this._isMounted && this.setState(previousState => { - const newItemIdToOpenActionsPopoverMap = { - ...previousState.itemIdToOpenActionsPopoverMap, - [itemId]: false, - }; - - return { - itemIdToOpenActionsPopoverMap: newItemIdToOpenActionsPopoverMap, - }; - }); - } - }; - - isPopoverOpen = itemId => { - return this.state.itemIdToOpenActionsPopoverMap[itemId]; - }; - - renderSelectAll = mobile => { - if (!this.state.isLoading && this.state.agents.length) { - return ( - - ); - } - }; - - getTableMobileSortItems() { - const items = []; - this.columns.forEach(column => { - if (column.isCheckbox || !column.isSortable) { - return; - } - items.push({ - name: column.label, - key: column.id, - onSort: this.onSort.bind(this, column.id), - isSorted: this.state.sortField === column.id, - isSortAscending: this.state.sortDirection === 'asc', - }); - }); - return items.length ? items : null; - } - - renderHeaderCells() { - const headers = []; - - this.columns.forEach((column, columnIndex) => { - if (column.isCheckbox) { - headers.push( - - - ); - } else { - headers.push( - - {column.label} - - ); - } - }); - return headers.length ? headers : null; - } - - renderRows() { - const renderRow = item => { - const cells = this.columns.map(column => { - const cell = item[column.id]; - - let child; - - if (column.isCheckbox) { - return ( - - {}} - type="inList" - /> - - ); - } - - if (column.render) { - child = column.render(item[column.id]); - } else { - child = cell; - } - - return ( - - {child} - - ); - }); - - return ( - await this.selectAgentAndApply(item.id)} - hasActions={true} - > - {cells} - - ); - }; - - const rows = []; - - for ( - let itemIndex = (this.state.pageIndex * this.state.itemsPerPage) % this.state.itemsPerPage; - itemIndex < - ((this.state.pageIndex * this.state.itemsPerPage) % this.state.itemsPerPage) + - this.state.itemsPerPage && this.state.agents[itemIndex]; - itemIndex++ - ) { - const item = this.state.agents[itemIndex]; - rows.push(renderRow(item)); - } - - return rows; } - renderFooterCells() { - const footers = []; - - const items = this.state.agents; - const pagination = { - pageIndex: this.state.pageIndex, - pageSize: this.state.itemsPerPage, - totalItemCount: this.state.totalItems, - pageSizeOptions: [10, 25, 50, 100] - }; - - this.columns.forEach(column => { - const footer = this.getColumnFooter(column, { items, pagination }); - if (column.mobileOptions && column.mobileOptions.only) { - return; // exclude columns that only exist for mobile headers - } - - if (footer) { - footers.push( - - {footer} - - ); - } else { - footers.push( - - {undefined} - - ); - } - }); - return footers; - } - - getColumnFooter = (column, { items, pagination }) => { - if (column.footer === null) { - return null; - } - if (column.footer) { - return column.footer; - } - - return undefined; - }; - - async onQueryChange(result) { - this._isMounted && - this.setState({ isLoading: true, ...result }, async () => { - await this.getItems(); - }); - } - - getSelectedItems(){ - return Object.keys(this.state.itemIdToSelectedMap).filter(x => { - return (this.state.itemIdToSelectedMap[x] === true) - }) - } - - unselectAgents(){ - this._isMounted && this.setState({itemIdToSelectedMap: {}}); + unselectAgents() { store.dispatch(updateCurrentAgentData({})); this.props.removeAgentsFilter(); } - getSelectedCount(){ - return this.getSelectedItems().length; - } - - async selectAgentAndApply(agentID){ - try{ - const data = await WzRequest.apiReq('GET', '/agents', { params: { q: 'id=' + agentID}}); - const formattedData = data.data.data.affected_items[0] //TODO: do it correctly + async selectAgentAndApply(agentID) { + try { + const data = await WzRequest.apiReq('GET', '/agents', { + params: { q: 'id=' + agentID }, + }); + const formattedData = data?.data?.data?.affected_items?.[0]; store.dispatch(updateCurrentAgentData(formattedData)); this.props.updateAgentSearch([agentID]); - } catch(error) { + } catch (error) { store.dispatch(updateCurrentAgentData({})); this.props.removeAgentsFilter(true); const options = { @@ -546,52 +115,42 @@ export class AgentSelectionTable extends Component { } } - showContextMenu(id){ - this._isMounted && this.setState({contextMenuId: id}) - } + addIconPlatformRender(agent) { + let icon = ''; + const os = agent?.os || {}; - addIconPlatformRender(os) { - if(typeof os === "string" ){ return os}; - let icon = false; - - if (((os || {}).uname || '').includes('Linux')) { + if ((os?.uname || '').includes('Linux')) { icon = 'linux'; - } else if ((os || {}).platform === 'windows') { + } else if (os?.platform === 'windows') { icon = 'windows'; - } else if ((os || {}).platform === 'darwin') { + } else if (os?.platform === 'darwin') { icon = 'apple'; } - const os_name = - checkField((os || {}).name) + - ' ' + - checkField((os || {}).version); + const os_name = `${agent?.os?.name || ''} ${agent?.os?.version || ''}`; + return ( - - {' '} - {os_name === '--' ? '-' : os_name} - + + + + {' '} + {os_name.trim() || '-'} + ); } - filterGroupBadge = (group) => { - const { filters } = this.state; - let auxFilters = filters.map( filter => filter.value.match(/group=(.*S?)/)[1] ); - if (filters.length > 0) { - !auxFilters.includes(group) ? - this.setState({ - filters: [...filters, {field: "q", value: `group=${group}`}], - }) : false; - } else { - this.setState({ - filters: [...filters, {field: "q", value: `group=${group}`}], - }) - } - } + filterGroupBadge = group => { + this.setState({ + filters: { + default: { q: 'id!=000' }, + q: `group=${group}`, + }, + }); + }; - renderGroups(groups){ + renderGroups(groups) { return Array.isArray(groups) ? ( - ) : groups + {...this.props} + /> + ) : ( + groups + ); } render() { - const pagination = { - pageIndex: this.state.pageIndex, - pageSize: this.state.itemsPerPage, - totalItemCount: this.state.totalItems, - pageCount: - this.state.totalItems % this.state.itemsPerPage === 0 - ? this.state.totalItems / this.state.itemsPerPage - : parseInt(this.state.totalItems / this.state.itemsPerPage) + 1, - }; const selectedAgent = store.getState().appStateReducers.currentAgentData; + const getRowProps = (item, idx) => { + return { + 'data-test-subj': `explore-agent-${idx}`, + className: 'customRowClass', + onClick: () => this.selectAgentAndApply(item.id), + }; + }; + return (
- - - this.setState({filters, pageIndex: 0})} - placeholder="Filter or search agent" - /> - - - {selectedAgent && Object.keys(selectedAgent).length > 0 && ( - + {/* agent name (agent id) Unpin button right aligned, require justifyContent="flexEnd" in the EuiFlexGroup */} - - + + {selectedAgent.name} ({selectedAgent.id}) - + this.unselectAgents()} - iconType="pinFilled" - aria-label="unpin agent" + iconType='pinFilled' + aria-label='unpin agent' /> - + )} - - - - - - - - - - - {this.renderHeaderCells()} - {(this.state.agents.length && ( - - {this.renderRows()} - - )) || ( - - - - {this.state.isLoading ? 'Loading agents' : 'No results found'} - - - - )} - - - - - { + return { + ...item, + /* + The agent version contains the Wazuh word, this get the string starting with + v + */ + ...(typeof item.version === 'string' + ? { version: item.version.match(/(v\d.+)/)?.[1] } + : { version: '-' }), + }; + }} + rowProps={getRowProps} + filters={this.state.filters} + searchTable + searchBarWQL={{ + options: searchBarWQLOptions, + suggestions: { + field(currentValue) { + return [ + { label: 'id', description: 'filter by id' }, + { label: 'group', description: 'filter by group' }, + { label: 'name', description: 'filter by name' }, + { + label: 'os.name', + description: 'filter by operating system name', + }, + { + label: 'os.version', + description: 'filter by operating system version', + }, + { label: 'status', description: 'filter by status' }, + { label: 'version', description: 'filter by version' }, + ]; + }, + value: async (currentValue, { field }) => { + try { + switch (field) { + case 'status': + return UI_ORDER_AGENT_STATUS.map(status => ({ + label: status, + })); + default: { + const response = await WzRequest.apiReq( + 'GET', + '/agents', + { + params: { + distinct: true, + limit: SEARCH_BAR_WQL_VALUE_SUGGESTIONS_COUNT, + select: field, + sort: `+${field}`, + ...(currentValue + ? { + q: `${searchBarWQLOptions.implicitQuery.query}${searchBarWQLOptions.implicitQuery.conjunction}${field}~${currentValue}`, + } + : { + q: `${searchBarWQLOptions.implicitQuery.query}`, + }), + }, + }, + ); + if (field === 'group') { + /* the group field is returned as an string[], + example: ['group1', 'group2'] + + Due the API request done to get the distinct values for the groups is + not returning the exepected values, as workaround, the values are + extracted in the frontend using the returned results. + + This API request to get the distint values of groups doesn't + return the unique values for the groups, else the unique combination + of groups. + */ + return response?.data?.data.affected_items + .map(item => getLodash(item, field)) + .flat() + .filter( + (item, index, array) => + array.indexOf(item) === index, + ) + .sort() + .map(group => ({ label: group })); + } + return response?.data?.data.affected_items.map(item => ({ + label: getLodash(item, field), + })); + } + } + } catch (error) { + return []; + } + }, + }, + }} />
);