From 26a01f05bebc06f11dc91378dd32e417a0c831f7 Mon Sep 17 00:00:00 2001 From: Antonio <34042064+Desvelao@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:50:57 +0100 Subject: [PATCH] Merge 4.10.0 into 4.10.1 (#7140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump 4.10.0 rev 03 (#7110) chore: bump 4.10.0 rev 03 * Substitute warning icon in events view (#7057) * Sustitude warning icon * Update Changelog * Change to iInCircle * Solve aria-label comments and fixed icon centered with text * Solve aria-label nitpick * fix: aria-label in some buttons * fix: aria-label --------- Co-authored-by: Antonio David Gutiérrez * Fix scroll in vulnerabilities inventory table (#7118) * Remove fixed height in Vulnerabilities Inventory table * Add changelog * Fix ability to filter form FIM details (#7119) * fix(fim): fix ability to filter from flyout details in FIM inventory - Fix FIM inventory flyout filters for Files and Registry tabs - Add ability to filter from Last analysis and Last modified fields from Registry details - Add ability to select the visible columns in the Files and Registry tables - Add to Files table the columns: date (hidden), md5 (hidden), sha1 (hidden), sha256 (hidden) - Add to Registry table the columns: date (hidden) - Renamed Last Modified to Last modified in the Files table * chore(changelog): add entry * Remove processes state column in macOS agents (#7122) * Refactor process column mapping to use a dedicated function for improved readability and maintainability * Update CHANGELOG to document removal of processes state column in macOS agents (#7122) * Refactor process column mapping to use `mapColumns` for consistency across OS implementations and enhance code clarity * Remove unused 'State' column from inventory snapshot for better clarity in agent rendering tests * Fix agents chart loading state (#7120) * Fix agents chart loading state * Add changelog * Create agents management menu and move sections (#7112) * create agents management menu and move sections * update changelog * move sections to a different location * Changes in the categories * changes in the categories * changes in the categories --------- Co-authored-by: Antonio David Gutiérrez * Bump 4.9.2 rev 00 (#7126) * chore: bump 4.9.2 rev 00 * chore(changelog): add entry * feat(bump): add ability to edit the imposter specification file reference on bump script of main plugin * Fix vulnerability inventory table scroll (#7128) * Fix vulnerability inventory table scroll * Add changelog * Fixed border on cells in events disappear (#7075) * Overwrite eui styles * Added changelog * Solve styles * Change border per shadow-box * Fix changelog --------- Co-authored-by: Federico Rodriguez * Bump 4.9.2 rev01 (#7134) chore: bump 4.9.2 rev01 * Redesign inventory, stats and configuration page to use tabs (#7089) * Add toTitleCase function for changing text to title case * Update MainModuleAgent component to display section in EuiTabs * Update module.scss styles for module header agent * Change stats page to use tabs in Wazuh app * Refactor useAsyncAction function with TypeScript * Refactor AgentsWelcome component structure and layout * Refactor conditional rendering logic for main agent module * Remove unused EuiTitle import and euiThemeVars * Add EuiPanel around AgentInfo component in MainModuleAgent * Update indexPattern access to handle null values safely * Fix Prettier issue * Remove global breadcrumb from MainModuleAgent component * Update AgentStats component with AgentInfo component * Add AgentInfo component and update configuration options * Fix Prettier issue * Update stats and configuration page to use tabs * feat: add AgentInfo component to display agent details with customizable stats and styling. Includes initial SCSS for layout * fix: update import path for AgentInfo and adjust EuiSpacer size to improve layout in agent stats component * style: add overflow-y auto to visualization layout for improved scroll handling on small screens * refactor: simplify layout in SyscollectorInventory by removing unnecessary EuiFlexGroups for better readability and performance * refactor: remove unused styles in welcome component for cleaner SCSS and enhanced maintainability * refactor: update import path for AgentInfo in agents-welcome for improved file organization and clarity * refactor: remove unused .wz-module-header-agent styles for cleaner SCSS and improved code maintainability * refactor: remove unused import of Agent in endpoints-summary for cleaner code and improved maintainability * refactor: simplify MainModuleAgent component by removing unnecessary class for cleaner markup and improved readability * refactor: enhance SyscollectorInventory with EuiPanel and AgentInfo for improved layout and readability * refactor: update import path for AgentInfo to improve module structure and clarity in main-agent component * refactor: use clsx for conditional class assignment in MainModuleAgent component for cleaner markup and improved maintainability * refactor: adjust margin for .wz-module-header-agent to improve layout consistency in module.scss * refactor: fine-tune margin for EuiFlexItem in MainModuleOverview to enhance layout consistency and visual alignment * refactor: refine EuiFlexGroup and EuiFlexItem margins in MainModuleAgent for better layout consistency and visual alignment * refactor: update SyscollectorInventory to use EuiPage for improved layout structure and consistency in inventory.tsx * refactor: update Prettier and VSCode settings for consistent formatting and improved layout with adjusted print width and arrow parens * refactor: adjust font size and weight for .euiTab__content in module.scss to enhance layout and text readability * refactor: streamline MainModuleAgent component by simplifying conditional rendering and enhancing code readability * refactor: simplify component structure by removing unnecessary divs and styles related to agent info for cleaner code and UI * refactor: remove padding and min-height from welcome agent tabs for a cleaner dark theme UI design * refactor: remove unused variable in AgentsWelcome component for improved code clarity and maintainability * refactor: add margin-right to ButtonExploreAgent for better alignment in AgentsWelcome component UI * refactor: simplify import statements and improve code consistency in AgentsWelcome component for better readability and maintainability * refactor: adjust indentation for improved readability in MainModuleAgent component's JSX structure * refactor: streamline formatting and improve readability in AgentInfo component's JavaScript logic and JSX structure * refactor: update marginTop style in MainModuleAgent component for better visual alignment in the rendered output * feat: add SECTIONS enum to define constants for different sections in the application for improved organization and clarity * feat: create AgentTabs constants for improved organization of agent-related sections in the application * refactor: remove unused sections constants from WzMenu for cleaner code and improved maintainability * feat: integrate AgentTabs constants into AgentsWelcome for consistent tab handling and cleaner code structure * fix: update Endpoint summary link to use constants for navigation and improve consistency in routing across the application * feat: enhance routing in AgentView by utilizing AgentTabs constants for improved navigation consistency and cleaner code structure * refactor: streamline AgentView implementation by consolidating imports and utilizing AgentTabs for cleaner tab management * refactor: replace hardcoded route paths with SECTIONS constants for improved maintainability and consistency in app-router.tsx * refactor: update MainModuleAgent to use AgentTabs for improved tab management and clean up loading state handling * refactor: optimize ButtonExploreAgent rendering and streamline ComplianceTable component usage for cleaner code structure * feat: add Network component for displaying agent network details, including interfaces, ports, and settings in a structured layout * feat: introduce Software component to display agent software details, including platform-specific packages and updates management * feat: enhance AgentView to include MainSyscollector and enable switchTab functionality for improved agent details display * feat: update MainModuleAgent to support switchTab functionality for improved navigation across agent sections * feat: refactor SyscollectorInventory to conditionally render Software and Network components based on selected section * feat: simplify imports and adjust layout in SyscollectorInventory for improved readability and structure * feat: rename Network and Software components to NetworkTab and SoftwareTab for consistency and clarity in SyscollectorInventory * feat: add ProcessesTab component to Syscollector for displaying agent processes within the inventory view * feat: refactor SyscollectorInventory to improve component imports and update layout for better structure and readability * feat: update CHANGELOG to reflect change of inventory, stats, and configuration pages to use tabs for improved navigation * feat: clean up SyscollectorInventory layout by removing redundant direction prop in EuiFlexGroup components for better clarity * feat: streamline component imports and formatting in various files for improved code cleanliness and consistency * Revert prettier file * Revert Prettier and .vscode/settings.json * Revert ,vscode/settings.json * Fix Prettier issue * Refactor MainModuleAgent to use inventoryTabs for rendering tabs dynamically * Update inventory tests to use AgentTabs for network section rendering in SyscollectorInventory * Update inventory snapshots and tests for network ports table rendering across agents with correct titles and columns * Update inventory snapshots and tests for network interfaces and settings table rendering with correct titles and columns across agents * Refactor inventory tests to enhance structure and maintainability for network settings and interfaces rendering across agents * Update inventory snapshots and tests for software and Windows updates tables, ensuring correct rendering across agents with titles and columns * Update inventory snapshots and tests for processes table rendering, ensuring correct columns and titles for all agent types * Add data-test-subj for agent info in snapshots and tests, ensuring accurate rendering for Linux, Windows, and Apple agents * Refactor agent-stats component by removing redundant statsAgents definition, simplifying header column structure for better clarity * Add tests for AgentStats component, ensuring it correctly renders agent info and integrates with mock services * Add tests for WzConfigurationSwitch component, ensuring it correctly renders agent info and integrates with mock services * Add data-test-subj attributes to multiple table components for improved testing consistency in snapshots * Fix Prettier issue * Enhance type annotations in ReportingService for improved clarity and TypeScript compatibility * Add data-test attributes for improved testing in MainModuleAgent and GenerateReportButton components * Improve type safety in MainModuleAgent and GenerateReportButton by specifying Agent type for agent prop * Remove unused imports and instances of ReportingService and FilterHandler in MainModuleAgent for cleaner code * Remove unused imports in MainModuleAgent for cleaner code and improved maintainability * Remove unused state property 'switchModule' in MainModuleAgent for cleaner code and improved maintainability * Remove unused state property 'selectView' in MainModuleAgent for cleaner code and improved maintainability * Fix optional chaining for renderTabs method in MainModuleAgent to improve code robustness and maintainability * Refactor tab layout in MainModuleAgent for improved readability and consistent formatting in JSX structure * Add unit tests for MainModuleAgent to validate tab rendering behavior across sections for better test coverage and reliability * Add unique key prop to EuiTab in MainModuleAgent for better performance and to prevent React warnings during rendering * Update MainModuleAgent and tests for dynamic report tab rendering based on section, ensuring accurate content display * Refactor MainModuleAgent tests to use Testing Library, improving readability and adding click handler verification for tab switching * Refactor WzConfigurationSwitch tests to use Testing Library, enhancing clarity and ensuring proper agent info rendering * Refactor MainAgent tests to use constants for report tab identifiers, enhancing readability and maintainability of the tests * Refactor AgentStats tests to utilize queryDataTestAttr for agent info, enhancing test readability and maintainability * Refactor AgentStats test to use container and queryDataTestAttr, improving test readability and maintainability * Fix Prettier issue * Update report tab constants in main-agent tests for consistency with new naming conventions * Refactor AgentStats test to use wrapper for querying agent info, enhancing readability and maintainability * Enhance WzManagementConfiguration to always show agent info and improve conditionals in WzConfigurationSwitch for better clarity * Add data-test-subj to EuiSpacer and update tests for WzConfigurationSwitch to handle showAgentInfo condition better * Remove outdated group display logic in WzConfigurationSwitch to improve component clarity and reduce unnecessary complexity * Fix Prettier issue * style(visualizations): remove overflow-y property from visualization layout for better responsiveness on small screens * refactor(configuration-switch): streamline agent grouping display and remove redundant agent info section from component and tests * refactor(agent-stats): enhance stats display with structured fields and loading spinner, removing old agent info test case * refactor(main-agent): simplify section checks by limiting tabs to SOFTWARE, NETWORK, and PROCESSES for improved readability and performance * refactor(main-agent): destructure props for improved readability and simplify tab checks in rendering logic * test(main-agent): add tests to verify absence of generate report button and agent info ribbon in WzConfigurationSwitch component * test(inventory): remove STATE column from processes table test for accurate representation of data * test(agent-stats): add tests to verify absence of generate report button and agent info ribbon in AgentStats component * test(agent-stats): simplify imports in agent-stats test file for cleaner code organization * fix(wz-text-with-tooltip): refactor props interface and clean up component for better readability and maintainability * fix(group-truncate): update filterAction type and simplify action function for improved clarity and type safety * refactor(agent-info): migrate agents-info.js to TypeScript for improved type safety and maintainability * refactor(agent-info): rename agents-info to agent-info for consistent naming and improved clarity in imports * fix(group-truncate): make action type explicit and handle optional filterAction for better clarity and type safety * refactor(agent-info): rename checkField to renderField for improved clarity in rendering agent information * refactor(agent-info): simplify getOsName method for clearer logic and improved readability of OS name and version handling * refactor(agent-status): enhance type safety in AgentStatus props and clarify status handling with TypeScript types * refactor(agent-info): streamline agent stats structure and replace WzStat with RibbonItem for better component organization * refactor(agent-info): update labels to use RibbonItemLabel constants for improved consistency and clarity in agent stats display * refactor(agent-info): simplify agent stats display by replacing specific fields with the entire agent object for consistency * refactor(api-response): add api_version field to IData interface for better API version management and clarity * refactor(agent-info): remove direct agent reference from RibbonItem, enhancing component flexibility and consistency * feat(ribbon-item): implement RibbonItem component for enhanced display of agent information with flexible styling and tooltips * refactor(ribbon-item): rename renderTitle to renderValue for improved clarity in value rendering logic * feat(ribbon): add Ribbon component for flexible ribbon layout, enhancing agent info display in the application * refactor(ribbon-item): rename class from wz-agent-info to wz-ribbon-item for better consistency in styling across components * refactor(ribbon): rename Ribbon to WzRibbon for consistent naming across components and improve clarity in the codebase * refactor(syscollector-metrics): simplify rendering by utilizing WzRibbon for improved readability and maintainability * refactor(inventory): replace AgentInfo with InventoryMetrics to streamline inventory rendering and enhance code clarity * refactor(ribbon): add loading state and improve structure by wrapping WzRibbon in EuiPanel for better layout management * refactor(syscollector-metrics): streamline imports and use lodash directly for emptiness checks to enhance code clarity * refactor(syscollector-metrics): replace static label with constant for improved consistency and maintainability * refactor(syscollector-metrics): increase maxWidth for CPU styling to improve display consistency in metrics component * refactor(ribbon-item): enhance OS detection by adding support for Ubuntu in the ribbon item component * refactor(ribbon-item): adjust styling to incorporate font size constant and optional style for better design adaptability * refactor(ribbon-item): add optional icon property to ribbon items for improved visual representation in the UI * refactor(agents-welcome): simplify layout by removing unnecessary EuiPanel wrapper around AgentInfo component * refactor(main-agent): streamline rendering logic by removing redundant state and simplify component structure for better readability * refactor(ribbon): add unique keys for ribbon items to enhance rendering performance and avoid React warnings * refactor(syscollector-metrics): add unique keys to ribbon items for improved rendering and to eliminate React warnings * refactor(agent-stats): replace panel with WzRibbon for better presentation and streamline loading state rendering logic * Fix Prettier issue * refactor(ribbon): add SCSS styling for ribbon item and remove unused agent info styles to streamline components * refactor(syscollector): replace EuiPanel with EuiPageBody for improved layout and streamline agent status display logic * style(constants): fix formatting issue in AGENT_STATUS_CODE array to align with code style guidelines * chore(tests): update snapshots and refactor inventory tests for better consistency and maintainability * Fix Prettier issue * feat(tests): enhance WzConfigurationSwitch tests and add CSS selector utility for querying test attributes * feat(ui): rename report-tabs to agent-tabs for improved clarity in MainModuleAgent component * feat(tests): update test descriptions and selectors to reflect renaming of report tabs to agent tabs in MainModuleAgent tests * feat(tests): refine agent-stats and main-agent tests to include checks for generate report button presence and descriptions * feat(tests): add inventory metrics tests for Debian agent in SyscollectorInventory components across software, network, and processes sections * feat(tests): improve selector readability in main-agent tests by extracting aria-selected constant for consistency * feat(tests): enhance main-agent tests with comprehensive coverage for all tab sections and generate report button assertions * feat(tests): add ribbon rendering tests in AgentStats to ensure accurate display of status and metrics attributes * fix(tests): correct syntax in AgentStats test for improved readability and maintainability of ribbon item assertions * feat(tests): add unit tests for InventoryMetrics to validate rendering of syscollector metrics and ribbon items * fix(wz-agents): change agent parameter type to optional for better flexibility in getAgentOSType function * fix(agent-info/syscollector): update labels and key assignments for better clarity in metrics display and styling adjustments * fix(ribbon): adjust padding size and justify content for better layout in the ribbon component * fix(ribbon): update label formats to lowercase and switch from label to key for item identification in ribbon component * fix(ribbon): add conditional rendering to prevent empty icon display in ribbon item component * fix(agent-group-truncate): enhance button styling with specified font weight and size for improved consistency * fix(snapshot): update ribbon item data-test attribute to reflect correct label for operating system and adjust styles accordingly * fix(reporting): change default value of agents parameter from string to false in startVis2Png method * fix(agent): remove unnecessary prop from WzManagementConfiguration in AgentView component * fix(ribbon): rename STATUS label to AGENT_STATUS in ribbon and agent info components * fix(agent-info): update label casing for agent status and group in AgentInfo component --------- Co-authored-by: Chantal Belén kelm <99441266+chantal-kelm@users.noreply.github.com> * Add filter by value to document details fields (#7081) * Refactor filter button actions in DocViewer * Add Doc Viewer styles and import to Doc Viewer component * Refactor cell filter actions to use 'field' instead of 'columnId' * Refactor filtering logic in DocViewer component * Add onFilterHandler to DocViewer component * Add onFilter handler to various components * Refactor RequirementFlyout component for readability * Add filter functionality to inventory vulnerabilities dashboard * Remove unused code related to doc viewer in WazuhDataGrid * Add setFilters function to FileDetails and RequirementFlyout * Refactor doc-viewer styles for button display * Add filter state management to data grid and drilldown panels * Fix Prettier issue * Add filter by value to document details fields * Update defaultColumns to defaultTableColumns in WazuhDataGrid * Add filter functionality for document viewer * Swap the order of arguments in filter action test * Rename button icons to EuiButtonIcon component * Update component import paths and class names * Update doc-viewer styles for wzDocViewer classes * Refactor adding filters to handle array values efficiently * Add onClose callback to onFilter function in DocViewer * Refactor DocumentViewTableAndJson component props * Refactor technique row details component props type * Add setFilters function to TechniqueRowDetails component * Refactor closeFlyoutHandler in DashboardTH component * Add closeFlyoutHandler function for flyout onClose event * Fix Prettier issue * fix: add guard clause for undefined value in filter cell actions * feat: add FilterStateStore enum and update state management in PatternDataSourceFilterManager to use it * fix: update onFilter function to accept more specific FILTER_OPERATOR types and value types in DocViewer component * feat: add isNullish utility function and update exports in util index for better nullish checks * fix: refactor onFilterCellActions to use isNullish for improved handling of FILTER_OPERATOR and value inputs in data grid service * fix: refactor onFilterCellActions import path for improved structure and maintainability in data-grid and doc-viewer components * fix: update onFilterCellActions to accept number type in values for enhanced filtering capabilities in data grid component * test: add unit tests for onFilterCellActions to verify filtering with number values in data grid component * test: simplify onFilterCellActions tests by removing unused filters array for clearer unit tests in data grid component * test: enhance onFilterCellActions tests by clarifying filter addition with improved descriptions for number and string values in data grid * test: refactor buildFilter usage in onFilterCellActions tests for improved clarity on filter creation in data grid component * test: improve onFilterCellActions tests with clearer descriptions for filters and added date filter cases in data grid component * test: add tests for single filter actions with string values and 'is'/'is not' operators in onFilterCellActions for data grid * test: add tests for multiple filters with 'is' and 'is not' operators for rule.groups in onFilterCellActions for data grid * test: add test for onFilterCellActions with undefined value to ensure appropriate filter behavior in data grid component * test: update filter negate logic and add test for 'is not' operator with undefined value in onFilterCellActions for data grid * Fix Prettier issue * test: refactor filter creation logic in onFilterCellActions tests for improved clarity and consistency in data grid component * test: unify key usage in filter tests in onFilterCellActions for clearer and more maintainable assertions in data grid component * test: add filter logic tests for boolean values in onFilterCellActions for better coverage in data grid component * test: update filter tests in onFilterCellActions to clarify handling of boolean values in data grid component * test: improve clarity in filter cell action tests by specifying value types in data grid component assertions * test: refine value type definition in onFilterCellActions to enhance clarity for boolean and numeric filters in data grid component * Refactor drilldown components to simplify destructuring of props, removing unused variables for cleaner code * Remove unused `filters` and `setFilters` props from OfficePanel for cleaner code and improved readability * Fix typos in export button label across data grid components --------- Co-authored-by: Federico Rodriguez * Agent view Mitre ATT&CK exception (#7116) * add a validation to filterParams and filterParams.filters * add key to map * update changelog * change url * Fix url from endpoint summary to dashboard and events in mitre * Correction of link to technique in intellicense flyout * Go to tactics in intellicense, clean code and eui accordion start open * clean code * clean code * update changelog * clean code * fix card endpoint summary * update changelog * rename function * rename function goToTechniqueInIntelligence * rename function goToTechniqueInIntelligence --------- Co-authored-by: Federico Rodriguez * Add vulnerability detection card in agent overview (#7085) * Add top packages component * Add vuls severity stat component * Create vuls panel * Add vuls panel in agent overview * Update CHANGELOG * Apply prettier * Fix responsive behavior * Apply prettier * Change vuls panel style * Use vuls data source in agent overview panel * Move vuls panel * Change redirect in hoc * Remove unnecessary redirect * Add HOC to create pattern if not exist * Apply prettier * Remove unused imports Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> * Remove unused imports Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> * Remove unused code * Apply prettier * Resolve conflicts in agents-welcome * Change agents management menu icon --------- Co-authored-by: Chantal Belén kelm <99441266+chantal-kelm@users.noreply.github.com> Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> Co-authored-by: Federico Rodriguez * Remove duplicated entry was included in a previous version (#7139) chore(changelog): remove duplicated entry was included in a previous release * chore: update imposter specification reference --------- Co-authored-by: JuanGarriuz Co-authored-by: Federico Rodriguez Co-authored-by: Guido Modarelli <38738725+guidomodarelli@users.noreply.github.com> Co-authored-by: Chantal Belén kelm <99441266+chantal-kelm@users.noreply.github.com> Co-authored-by: Maximiliano Ibarra <6089438+Machi3mfl@users.noreply.github.com> --- CHANGELOG.md | 25 +- docker/imposter/wazuh-config.yml | 2 +- plugins/main/common/constants.ts | 5 + .../main/common/services/wz_agent_status.ts | 31 +- plugins/main/public/app-router.tsx | 29 +- .../public/components/agents/agent-status.tsx | 18 +- .../components/agents/fim/inventory.tsx | 55 +- .../agents/fim/inventory/fileDetail.tsx | 19 +- .../agents/fim/inventory/registry-table.tsx | 63 +- .../components/agents/fim/inventory/table.tsx | 94 +- .../agents/stats/agent-stats.test.tsx | 68 + .../components/agents/stats/agent-stats.tsx | 45 +- .../__snapshots__/inventory.test.tsx.snap | 22320 ++++++++++++---- .../syscollector/columns/process-columns.ts | 17 +- .../components/network-interfaces-table.tsx | 2 +- .../components/network-ports-table.tsx | 2 +- .../components/network-settings-table.tsx | 2 +- .../components/packages-table.tsx | 2 +- .../components/processes-table.tsx | 2 +- .../components/syscollector-metrics.test.tsx | 88 + .../components/syscollector-metrics.tsx | 189 +- .../components/windows-updates-table.tsx | 2 +- .../agents/syscollector/inventory.test.tsx | 704 +- .../agents/syscollector/inventory.tsx | 96 +- .../agents/syscollector/network.tsx | 33 + .../agents/syscollector/processes.tsx | 21 + .../agents/syscollector/software.tsx | 26 + .../data-grid/cell-filter-actions.test.tsx | 4 +- .../common/data-grid/cell-filter-actions.tsx | 28 +- .../common/data-grid/data-grid-service.ts | 25 +- .../data-grid/filter-cell-actions.test.ts | 265 + .../common/data-grid/filter-cell-actions.ts | 57 + .../pattern-data-source-filter-manager.ts | 17 +- .../common/doc-viewer/doc-viewer.scss | 94 + .../common/doc-viewer/doc-viewer.tsx | 75 +- .../doc-viewer/table-row-btn-filter-add.tsx | 76 + .../table-row-btn-filter-exists.tsx | 84 + .../table-row-btn-filter-remove.tsx | 75 + .../common/hooks/use_async_action.ts | 15 +- .../common/modules/main-agent.test.tsx | 359 + .../components/common/modules/main-agent.tsx | 229 +- .../common/modules/main-overview.tsx | 2 +- .../components/common/modules/module.scss | 31 +- .../common/modules/modules-defaults.tsx | 8 +- .../components/common/ribbon/ribbon-item.scss | 4 + .../components/common/ribbon/ribbon-item.tsx | 148 + .../components/common/ribbon/ribbon.tsx | 28 + .../common/search-bar/set-filters.ts | 25 + .../table-with-search-bar.test.tsx.snap | 2 + .../__snapshots__/table-wz-api.test.tsx.snap | 2 + .../common/tables/table-with-search-bar.tsx | 1 + .../components/common/tables/table-wz-api.tsx | 2 +- .../agent-group-truncate/group-truncate.tsx | 18 +- .../components/common/util/change-case.ts | 3 + .../public/components/common/util/index.ts | 1 + .../components/common/util/is-nullish.ts | 3 + .../common/wazuh-data-grid/wz-data-grid.tsx | 18 +- .../data-grid-additional-controls.tsx | 6 +- .../wazuh-discover/components/doc-details.tsx | 35 +- .../document-view-table-and-json.tsx | 27 +- .../common/wazuh-discover/wz-discover.tsx | 7 +- .../wazuh-discover/wz-flyout-discover.tsx | 18 +- .../common/welcome/agent-info/agent-info.tsx | 134 + .../components/common/welcome/agents-info.js | 232 - .../common/welcome/agents-welcome.js | 119 +- .../fim_events_table/fim_events_table.tsx | 2 +- .../common/welcome/components/index.ts | 1 + .../components/mitre_top/mitre-top.tsx | 88 +- .../components/top_packages_table/index.ts | 15 + .../top_packages_table/top_packages_table.tsx | 92 + .../vuls_panel/vuls_welcome_panel.tsx | 202 + .../vuls_severity_stat/vuls_severity_stat.tsx | 31 + .../components/common/welcome/welcome.scss | 15 - .../wz-text-with-tooltip-if-truncated.tsx | 64 +- .../endpoints-summary/agent/agent-tabs.ts | 8 + .../endpoints-summary/agent/index.tsx | 39 +- .../__snapshots__/agents-table.test.tsx.snap | 6 + .../compliance-table/compliance-table.tsx | 2 + .../requirement-flyout/requirement-flyout.tsx | 8 +- .../subrequirements/subrequirements.tsx | 2 + .../flyout-technique/flyout-technique.tsx | 86 +- .../technique-row-details.tsx | 37 +- .../components/techniques/techniques.tsx | 150 +- .../__snapshots__/intelligence.test.tsx.snap | 2 + .../public/components/overview/overview.tsx | 4 +- ...e-vulnerabilities-states-index-pattern.tsx | 12 +- .../dashboards/inventory/inventory.scss | 5 - .../dashboards/inventory/inventory.tsx | 13 +- .../main/public/components/wz-menu/wz-menu.js | 11 - .../configuration/configuration-switch.js | 527 +- .../configuration-switch.test.tsx | 206 + .../last-alerts-stat/last-alerts-stat.tsx | 38 +- .../controllers/overview/components/stats.js | 82 +- .../components/hits_counter/hits_counter.tsx | 58 +- .../interfaces/api-response.interface.ts | 1 + .../main/public/react-services/reporting.js | 7 +- .../main/public/react-services/wz-agents.ts | 7 +- plugins/main/public/sections.ts | 11 + plugins/main/public/styles/common.scss | 6 - .../public/styles/theme/dark/index.dark.scss | 13 - plugins/main/public/utils/applications.ts | 329 +- plugins/main/test/public/query-attr.ts | 8 + plugins/main/test/utils/CSS.ts | 27 + scripts/release/bump.js | 5 + scripts/release/lib/update-imposter.js | 43 + 105 files changed, 21612 insertions(+), 6888 deletions(-) create mode 100644 plugins/main/public/components/agents/stats/agent-stats.test.tsx create mode 100644 plugins/main/public/components/agents/syscollector/components/syscollector-metrics.test.tsx create mode 100644 plugins/main/public/components/agents/syscollector/network.tsx create mode 100644 plugins/main/public/components/agents/syscollector/processes.tsx create mode 100644 plugins/main/public/components/agents/syscollector/software.tsx create mode 100644 plugins/main/public/components/common/data-grid/filter-cell-actions.test.ts create mode 100644 plugins/main/public/components/common/data-grid/filter-cell-actions.ts create mode 100644 plugins/main/public/components/common/doc-viewer/doc-viewer.scss create mode 100644 plugins/main/public/components/common/doc-viewer/table-row-btn-filter-add.tsx create mode 100644 plugins/main/public/components/common/doc-viewer/table-row-btn-filter-exists.tsx create mode 100644 plugins/main/public/components/common/doc-viewer/table-row-btn-filter-remove.tsx create mode 100644 plugins/main/public/components/common/modules/main-agent.test.tsx create mode 100644 plugins/main/public/components/common/ribbon/ribbon-item.scss create mode 100644 plugins/main/public/components/common/ribbon/ribbon-item.tsx create mode 100644 plugins/main/public/components/common/ribbon/ribbon.tsx create mode 100644 plugins/main/public/components/common/search-bar/set-filters.ts create mode 100644 plugins/main/public/components/common/util/change-case.ts create mode 100644 plugins/main/public/components/common/util/is-nullish.ts create mode 100644 plugins/main/public/components/common/welcome/agent-info/agent-info.tsx delete mode 100644 plugins/main/public/components/common/welcome/agents-info.js create mode 100644 plugins/main/public/components/common/welcome/components/top_packages_table/index.ts create mode 100644 plugins/main/public/components/common/welcome/components/top_packages_table/top_packages_table.tsx create mode 100644 plugins/main/public/components/common/welcome/components/vuls_panel/vuls_welcome_panel.tsx create mode 100644 plugins/main/public/components/common/welcome/components/vuls_severity_stat/vuls_severity_stat.tsx create mode 100644 plugins/main/public/components/endpoints-summary/agent/agent-tabs.ts create mode 100644 plugins/main/public/controllers/management/components/management/configuration/configuration-switch.test.tsx create mode 100644 plugins/main/public/sections.ts create mode 100644 plugins/main/test/public/query-attr.ts create mode 100644 plugins/main/test/utils/CSS.ts create mode 100644 scripts/release/lib/update-imposter.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ecd37566dd..7c4568423c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,18 @@ All notable changes to the Wazuh app project will be documented in this file. - Support for Wazuh 4.10.1 -## Wazuh v4.10.0 - OpenSearch Dashboards 2.16.0 - Revision 02 +## Wazuh v4.10.0 - OpenSearch Dashboards 2.16.0 - Revision 03 ### Added - Support for Wazuh 4.10.0 - Added sample data for YARA [#6964](https://github.com/wazuh/wazuh-dashboard-plugins/issues/6964) - Added a custom filter and visualization for vulnerability.under_evaluation field [#6968](https://github.com/wazuh/wazuh-dashboard-plugins/issues/6968) [#7044](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7044) [#7046](https://github.com/wazuh/wazuh-dashboard-plugins/issues/7046) +- Add vulnerabilities card to agent details page [#7058](https://github.com/wazuh/wazuh-dashboard-plugins/issues/7058) +- Added an "Agents management" menu and moved the sections: "Endpoint Groups" and "Endpoint Summary" which changed its name to "Summary".[#7112](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7112) +- Added ability to filter from File Integrity Monitoring registry inventory [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119) +- Added new field columns and ability to select the visible fields in the File Integrity Monitoring Files and Registry tables [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119) +- Added filter by value to document details fields [#7081](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7081) ### Changed @@ -25,24 +30,42 @@ All notable changes to the Wazuh app project will be documented in this file. - Changed the agents summary in overview with no results to an agent deployment help message. [#7041](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7041) - Changed malware feature description [#7036](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7036) - Changed the font size of the kpi subtitles and the features descriptions [#7033](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7033) +- Changed the warning icon in events view to a info icon [#7057](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7057) - Changed feature container margins to ensure consistent separation and uniform design. [#7034](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7034) - Changed the initial width to the default columns on each selected field [#7059](https://github.com/wazuh/wazuh-dashboard-plugins/issues/7059) +- Changed inventory, stats and configuration page to use tabs [#7089](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7089) ### Fixed - Fixed the filter are displayed cropped on screens of 575px to 767px in vulnerability detection module [#7047](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7047) - Fixed read-only users could not access to Statistics application [#7001](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7001) - Fixed no-agent-alert spawn with selected agent in agent-welcome view [#7029](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7029) +- Fixed loading state of the agents status chart in the home overview [#7120](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7120) - Fixed security policy exception when it contained deprecated actions [#7042](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7042) +- Fixed border on cells in events that disappear when clicked [#7075](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7075) - Fixed export formatted csv data with special characters from tables [#7048](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7048) - Fixed column reordering feature [#7072](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7072) - Fixed filter management to prevent hiding when adding multiple filters [#7077](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7077) +- Fixed the Mitre ATT&CK exception in the agent view, the redirections of ID, Tactics, Dashboard Icon and Event Icon in the drop-down menu and the card not displaying information when the flyout was opened [#7116](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7116) +- Fixed the filter are displayed cropped on screens of 575px to 767px in vulnerability detection module [#7047](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7047) +- Fixed ability to filter from files inventory details flyout of File Integrity Monitoring [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119) ### Removed - Removed agent RBAC filters from dashboard queries [#6945](https://github.com/wazuh/wazuh-dashboard-plugins/pull/6945) - Removed GET /elastic/statistics API endpoint [#7001](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7001) - Removed VirusTotal application in favor of Malware Detection [#7038](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7038) +- Removed processes state column in macOS agents [#7122](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7122) + +## Wazuh v4.9.2 - OpenSearch Dashboards 2.13.0 - Revision 01 + +### Added + +- Support for Wazuh 4.9.2 + +### Fixed + +- Fixed vulnerabilities inventory table scroll [#7128](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7128) ## Wazuh v4.9.1 - OpenSearch Dashboards 2.13.0 - Revision 04 diff --git a/docker/imposter/wazuh-config.yml b/docker/imposter/wazuh-config.yml index 8f99c6897e..4e93ff790c 100755 --- a/docker/imposter/wazuh-config.yml +++ b/docker/imposter/wazuh-config.yml @@ -1,6 +1,6 @@ --- plugin: openapi -specFile: https://raw.githubusercontent.com/wazuh/wazuh/master/api/api/spec/spec.yaml +specFile: https://raw.githubusercontent.com/wazuh/wazuh/4.10.1/api/api/spec/spec.yaml system: stores: # this store is preloaded from file diff --git a/plugins/main/common/constants.ts b/plugins/main/common/constants.ts index 7b25a1d98c..4da147db02 100644 --- a/plugins/main/common/constants.ts +++ b/plugins/main/common/constants.ts @@ -530,3 +530,8 @@ export const OSD_URL_STATE_STORAGE_ID = 'state:storeInSessionStorage'; export const APP_STATE_URL_KEY = '_a'; export const GLOBAL_STATE_URL_KEY = '_g'; + +export enum FilterStateStore { + APP_STATE = 'appState', + GLOBAL_STATE = 'globalState', +} diff --git a/plugins/main/common/services/wz_agent_status.ts b/plugins/main/common/services/wz_agent_status.ts index 3c37e98a92..694e536917 100644 --- a/plugins/main/common/services/wz_agent_status.ts +++ b/plugins/main/common/services/wz_agent_status.ts @@ -1,14 +1,27 @@ -import { UI_COLOR_AGENT_STATUS, UI_LABEL_NAME_AGENT_STATUS, API_NAME_AGENT_STATUS } from '../constants'; +import { + UI_COLOR_AGENT_STATUS, + UI_LABEL_NAME_AGENT_STATUS, + API_NAME_AGENT_STATUS, +} from '../constants'; -type TAgentStatus = typeof API_NAME_AGENT_STATUS[keyof typeof API_NAME_AGENT_STATUS]; +export type TAgentStatus = + (typeof API_NAME_AGENT_STATUS)[keyof typeof API_NAME_AGENT_STATUS]; -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]; +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 agentStatusColorByAgentStatus( + status: TAgentStatus, +): TAgentStatusColor { + return UI_COLOR_AGENT_STATUS[status] || UI_COLOR_AGENT_STATUS.default; } -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 +export function agentStatusLabelByAgentStatus( + status: TAgentStatus, +): TAgentStatusLabel { + return ( + UI_LABEL_NAME_AGENT_STATUS[status] || UI_LABEL_NAME_AGENT_STATUS.default + ); +} diff --git a/plugins/main/public/app-router.tsx b/plugins/main/public/app-router.tsx index 842c07ae03..8f45a6b3cd 100644 --- a/plugins/main/public/app-router.tsx +++ b/plugins/main/public/app-router.tsx @@ -21,6 +21,7 @@ import { Settings } from './components/settings'; import { WzSecurity } from './components/security'; import $ from 'jquery'; import NavigationService from './react-services/navigation-service'; +import { SECTIONS } from './sections'; export function Application(props) { const dispatch = useDispatch(); @@ -66,33 +67,41 @@ export function Application(props) { - + - + - + } > - - + + } > } > diff --git a/plugins/main/public/components/agents/agent-status.tsx b/plugins/main/public/components/agents/agent-status.tsx index a7967a0d92..65179ba825 100644 --- a/plugins/main/public/components/agents/agent-status.tsx +++ b/plugins/main/public/components/agents/agent-status.tsx @@ -2,15 +2,29 @@ import React from 'react'; import { agentStatusColorByAgentStatus, agentStatusLabelByAgentStatus, + TAgentStatus, } from '../../../common/services/wz_agent_status'; import { ColumnWithStatusIcon } from './column-with-status-icon'; import { EuiIconTip } from '@elastic/eui'; import { AGENT_STATUS_CODE } from '../../../common/constants'; import '../../styles/common.scss'; +import { Agent } from '../endpoints-summary/types'; -export const AgentStatus = ({ status, children = null, style = {}, agent }) => { +interface AgentStatusProps { + status: TAgentStatus; + children?: string | null; + style?: React.CSSProperties; + agent?: Agent; +} + +export const AgentStatus = ({ + status, + children, + style = {}, + agent, +}: AgentStatusProps) => { const statusCodeAgent = AGENT_STATUS_CODE.find( - (status: StatusCodeAgent) => status.STATUS_CODE === agent?.status_code, + status => status.STATUS_CODE === agent?.status_code, ); return (
{ - this.setState({ filters }); - }; - - onTotalItemsChange = (totalItems: number) => { - this.setState({ totalItemsFile: totalItems }); - }; - onSelectedTabChanged = id => { this.setState({ selectedTabId: id }); }; - buildFilter(type) { - const filters = filtersToObject(this.state.filters); - const filter = { - ...filters, - limit: type === 'file' ? '15' : '1', - ...(type === 'registry' ? { q: 'type=registry_key' } : { type }), - ...(type === 'file' && { sort: '+file' }), - }; - return filter; - } - async getItemNumber(type: 'file' | 'registry') { try { const agentID = this.props.agent.id; const response = await WzRequest.apiReq('GET', `/syscheck/${agentID}`, { - params: this.buildFilter(type), + params: { + limit: 1, // reduce the size because only need the total items. 0 gives error + ...(type === 'registry' + ? { q: 'type=registry_key' } + : { q: 'type=file' }), + }, }); if (type === 'file') { return { @@ -257,8 +217,6 @@ export class Inventory extends Component { filters={filters} items={syscheck} totalItems={totalItemsFile} - onFiltersChange={this.onFiltersChange} - onTotalItemsChange={this.onTotalItemsChange} /> )} {selectedTabId === 'registry' && ( @@ -266,7 +224,6 @@ export class Inventory extends Component { {...this.props} filters={filters} totalItems={totalItemsRegistry} - onFiltersChange={this.onFiltersChange} /> )} diff --git a/plugins/main/public/components/agents/fim/inventory/fileDetail.tsx b/plugins/main/public/components/agents/fim/inventory/fileDetail.tsx index 0dff1a4698..899a0c9387 100644 --- a/plugins/main/public/components/agents/fim/inventory/fileDetail.tsx +++ b/plugins/main/public/components/agents/fim/inventory/fileDetail.tsx @@ -52,6 +52,7 @@ import { RedirectAppLinks } from '../../../../../../../src/plugins/opensearch_da import TechniqueRowDetails from '../../../overview/mitre/framework/components/techniques/components/flyout-technique/technique-row-details'; import { DATA_SOURCE_FILTER_CONTROLLED_CLUSTER_MANAGER } from '../../../../../common/constants'; import NavigationService from '../../../../react-services/navigation-service'; +import { setFilters } from '../../../common/search-bar/set-filters'; export class FileDetails extends Component { props!: { @@ -198,6 +199,7 @@ export class FileDetails extends Component { name: 'Last analysis', grow: 2, icon: 'clock', + link: true, transformValue: formatUIDate, }, { @@ -205,6 +207,7 @@ export class FileDetails extends Component { name: 'Last modified', grow: 2, icon: 'clock', + link: true, transformValue: formatUIDate, }, ]; @@ -290,21 +293,19 @@ export class FileDetails extends Component { } addFilter(field, value) { - const { filters, onFiltersChange } = this.props; - const newBadge: ICustomBadges = { field: 'q', value: '' }; + const { onFiltersChange } = this.props; + let filterUQL = ''; if (field === 'date' || field === 'mtime') { const value_max = moment(value).add(1, 'day'); - newBadge.value = `${field}>${moment(value).format( + filterUQL = `${field}>${moment(value).format( 'YYYY-MM-DD', - )} AND ${field}<${value_max.format('YYYY-MM-DD')}`; + )};${field}<${value_max.format('YYYY-MM-DD')}`; } else { - newBadge.value = `${field}=${ + filterUQL = `${field}=${ field === 'size' ? this.props.currentFile[field] : value }`; } - !filters.some( - item => item.field === newBadge.field && item.value === newBadge.value, - ) && onFiltersChange([...filters, newBadge]); + onFiltersChange({ q: filterUQL }); this.props.closeFlyout(); } @@ -586,6 +587,8 @@ export class FileDetails extends Component { this.discoverFilterManager.addFilters(newFilter); }} + filters={[]} + setFilters={setFilters(this.discoverFilterManager)} /> ); } diff --git a/plugins/main/public/components/agents/fim/inventory/registry-table.tsx b/plugins/main/public/components/agents/fim/inventory/registry-table.tsx index ee96fff10c..6dfb410c46 100644 --- a/plugins/main/public/components/agents/fim/inventory/registry-table.tsx +++ b/plugins/main/public/components/agents/fim/inventory/registry-table.tsx @@ -21,18 +21,10 @@ import { withRouterSearch } from '../../../common/hocs'; import { Route, Switch } from '../../../router-search'; import NavigationService from '../../../../react-services/navigation-service'; -const searchBarWQLOptions = { - implicitQuery: { - query: 'type=registry_key', - conjunction: ';', - }, -}; - -const searchBarWQLFilters = { default: { q: 'type=registry_key' } }; - export const RegistryTable = withRouterSearch( class RegistryTable extends Component { state: { + filters: {}; syscheck: []; isFlyoutVisible: Boolean; currentFile: { @@ -51,7 +43,7 @@ export const RegistryTable = withRouterSearch( super(props); this.state = { - syscheck: [], + filters: {}, isFlyoutVisible: false, currentFile: { file: '', @@ -73,12 +65,13 @@ export const RegistryTable = withRouterSearch( name: 'Registry', sortable: true, searchable: true, + show: true, }, { field: 'mtime', name: ( - Last Modified{' '} + Last modified{' '} + Last analysis{' '} + + + ), + sortable: true, + width: '100px', + render: formatUIDate, + searchable: false, }, ]; } + onFiltersChange = filters => { + this.setState({ + filters, + }); + }; + renderRegistryTable() { const getRowProps = item => { const { file } = item; @@ -111,6 +129,8 @@ export const RegistryTable = withRouterSearch( const columns = this.columns(); + const APIendpoint = `/syscheck/${this.props.agent.id}?type=registry_key`; + return ( @@ -118,11 +138,11 @@ export const RegistryTable = withRouterSearch( title='Registry' tableColumns={columns} tableInitialSortingField='file' - endpoint={`/syscheck/${this.props.agent.id}`} + endpoint={APIendpoint} searchBarWQL={{ - options: searchBarWQLOptions, suggestions: { field: () => [ + { label: 'date', description: 'filter by analysis time' }, { label: 'file', description: 'filter by file' }, { label: 'mtime', @@ -133,7 +153,7 @@ export const RegistryTable = withRouterSearch( try { const response = await WzRequest.apiReq( 'GET', - `/syscheck/${this.props.agent.id}`, + APIendpoint, { params: { distinct: true, @@ -142,12 +162,9 @@ export const RegistryTable = withRouterSearch( sort: `+${field}`, ...(currentValue ? { - // Add the implicit query - q: `${searchBarWQLOptions.implicitQuery.query}${searchBarWQLOptions.implicitQuery.conjunction}${field}~${currentValue}`, + q: `${field}~${currentValue}`, } - : { - q: `${searchBarWQLOptions.implicitQuery.query}`, - }), + : {}), }, }, ); @@ -174,11 +191,16 @@ export const RegistryTable = withRouterSearch( }, }, }} - filters={searchBarWQLFilters} + filters={this.state.filters} showReload downloadCsv={`fim-registry-${this.props.agent.id}`} searchTable={true} rowProps={getRowProps} + saveStateStorage={{ + system: 'localStorage', + key: 'wz-fim-registry-key-table', + }} + showFieldSelector /> @@ -201,6 +223,7 @@ export const RegistryTable = withRouterSearch( view='inventory' // showViewInEvents={true} {...this.props} + onFiltersChange={this.onFiltersChange} /> )} > diff --git a/plugins/main/public/components/agents/fim/inventory/table.tsx b/plugins/main/public/components/agents/fim/inventory/table.tsx index f9021891db..80157ce30c 100644 --- a/plugins/main/public/components/agents/fim/inventory/table.tsx +++ b/plugins/main/public/components/agents/fim/inventory/table.tsx @@ -21,19 +21,10 @@ import { withRouterSearch } from '../../../common/hocs'; import { Route, Switch } from '../../../router-search'; import NavigationService from '../../../../react-services/navigation-service'; -const searchBarWQLOptions = { - implicitQuery: { - query: 'type=file', - conjunction: ';', - }, -}; - -const searchBarWQLFilters = { default: { q: 'type=file' } }; - export const InventoryTable = withRouterSearch( class InventoryTable extends Component { state: { - syscheck: []; + filters: any; isFlyoutVisible: Boolean; currentFile: { file: string; @@ -46,14 +37,13 @@ export const InventoryTable = withRouterSearch( agent: any; items: []; totalItems: number; - onTotalItemsChange: Function; }; constructor(props) { super(props); this.state = { - syscheck: props.items, + filters: {}, isFlyoutVisible: false, currentFile: { file: '', @@ -80,12 +70,13 @@ export const InventoryTable = withRouterSearch( sortable: true, width: '250px', searchable: true, + show: true, }, { field: 'mtime', name: ( - Last Modified{' '} + Last modified{' '} + Last analysis{' '} + + + ), + sortable: true, + width: '100px', + render: formatUIDate, + searchable: false, + }, + { + field: 'md5', + name: 'MD5', + searchable: true, + sortable: true, + }, + { + field: 'sha1', + name: 'SHA1', + searchable: true, + sortable: true, + }, + { + field: 'sha256', + name: 'SHA256', + searchable: true, + sortable: true, }, ]; } + onFiltersChange = filters => { + this.setState({ + filters, + }); + }; + renderFilesTable() { const getRowProps = item => { const { file } = item; @@ -155,6 +194,8 @@ export const InventoryTable = withRouterSearch( }; const columns = this.columns(); + const APIendpoint = `/syscheck/${this.props.agent.id}?type=file`; + return ( @@ -162,18 +203,24 @@ export const InventoryTable = withRouterSearch( title='Files' tableColumns={columns} tableInitialSortingField='file' - endpoint={`/syscheck/${this.props.agent.id}`} + endpoint={APIendpoint} searchBarWQL={{ - options: searchBarWQLOptions, suggestions: { field: currentValue => [ + { label: 'date', description: 'filter by analysis time' }, { label: 'file', description: 'filter by file' }, { label: 'gid', description: 'filter by group id' }, { label: 'gname', description: 'filter by group name' }, + { label: 'md5', description: 'filter by MD5 checksum' }, { label: 'mtime', description: 'filter by modification time', }, + { label: 'sha1', description: 'filter by SHA1 checksum' }, + { + label: 'sha256', + description: 'filter by SHA256 checksum', + }, { label: 'size', description: 'filter by size' }, { label: 'uname', description: 'filter by user name' }, { label: 'uid', description: 'filter by user id' }, @@ -182,7 +229,7 @@ export const InventoryTable = withRouterSearch( try { const response = await WzRequest.apiReq( 'GET', - `/syscheck/${this.props.agent.id}`, + APIendpoint, { params: { distinct: true, @@ -191,12 +238,9 @@ export const InventoryTable = withRouterSearch( sort: `+${field}`, ...(currentValue ? { - // Add the implicit query - q: `${searchBarWQLOptions.implicitQuery.query}${searchBarWQLOptions.implicitQuery.conjunction}${field}~${currentValue}`, + q: `${field}~${currentValue}`, } - : { - q: `${searchBarWQLOptions.implicitQuery.query}`, - }), + : {}), }, }, ); @@ -223,11 +267,16 @@ export const InventoryTable = withRouterSearch( }, }, }} - filters={searchBarWQLFilters} + filters={this.state.filters} showReload downloadCsv={`fim-files-${this.props.agent.id}`} searchTable={true} rowProps={getRowProps} + saveStateStorage={{ + system: 'localStorage', + key: 'wz-fim-files-table', + }} + showFieldSelector /> @@ -251,6 +300,7 @@ export const InventoryTable = withRouterSearch( view='inventory' showViewInEvents={true} {...this.props} + onFiltersChange={this.onFiltersChange} /> )} > diff --git a/plugins/main/public/components/agents/stats/agent-stats.test.tsx b/plugins/main/public/components/agents/stats/agent-stats.test.tsx new file mode 100644 index 0000000000..84f8e2a648 --- /dev/null +++ b/plugins/main/public/components/agents/stats/agent-stats.test.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { render, act } from '@testing-library/react'; +import { AgentStats } from './agent-stats'; +import { queryDataTestAttr } from '../../../../test/public/query-attr'; +import { CSS } from '../../../../test/utils/CSS'; + +jest.mock('../../../react-services', () => ({ + WzRequest: { + apiReq: jest.fn().mockResolvedValue(undefined), + }, +})); + +describe('AgentStats', () => { + it('should not render agent info ribbon', async () => { + await act(async () => { + const { container } = render(); + + const agentInfoRibbon = container.querySelector( + queryDataTestAttr('agent-info'), + ); + expect(agentInfoRibbon).toBeFalsy(); + }); + }); + + it('should render stats info ribbon', async () => { + await act(async () => { + const { container } = render(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-status')), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr('ribbon-item-buffer_enabled'), + ), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-msg_buffer')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-msg_count')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-msg_sent')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-last_ack')), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr('ribbon-item-last_keepalive'), + ), + ).toBeTruthy(); + + expect( + container.querySelectorAll( + queryDataTestAttr('ribbon-item-', CSS.Attribute.Substring), + ), + ).toHaveLength(7); + }); + }); +}); diff --git a/plugins/main/public/components/agents/stats/agent-stats.tsx b/plugins/main/public/components/agents/stats/agent-stats.tsx index 2d57187359..98f966281c 100644 --- a/plugins/main/public/components/agents/stats/agent-stats.tsx +++ b/plugins/main/public/components/agents/stats/agent-stats.tsx @@ -47,6 +47,7 @@ import { import { getErrorOrchestrator } from '../../../react-services/common-services'; import { endpointSummary } from '../../../utils/applications'; import NavigationService from '../../../react-services/navigation-service'; +import WzRibbon from '../../common/ribbon/ribbon'; const tableColumns = [ { @@ -142,7 +143,8 @@ export const MainAgentStats = compose( ), )(AgentStats); -function AgentStats({ agent }) { +export function AgentStats(props) { + const { agent } = props; const [loading, setLoading] = useState(); const [dataStatLogcollector, setDataStatLogcollector] = useState({}); const [dataStatAgent, setDataStatAgent] = useState(); @@ -186,33 +188,20 @@ function AgentStats({ agent }) { return ( - - - - - {statsAgents.map(stat => ( - - - {stat.title}:{' '} - {loading ? ( - - ) : ( - - {dataStatAgent !== undefined - ? stat.render - ? stat.render(dataStatAgent[stat.field]) - : dataStatAgent?.[stat.field] - : '-'} - - )} - - - ))} - - - - - + ({ + key: stat.field, + label: stat.title, + isLoading: loading, + value: + dataStatAgent !== undefined + ? stat.render + ? stat.render(dataStatAgent[stat.field]) + : dataStatAgent?.[stat.field] + : '-', + }))} + /> +
+ +
+
+ + + Cores [object Object] +
+
+
+ +
+
+ + + Memory [object Object] +
+
+
+ +
+
+ + + Arch [object Object] +
+
+
+ +
+
+ + + Operating system [object Object] +
+
+
+ +
+
+ + + CPU [object Object] +
+ +
+ +
+
+ + + Host name [object Object] +
+ +
+ +
+
+ + + Board serial [object Object] +
+ +
+ +
+
+ + + Last scan [object Object] +
- -
+
-

- Network interfaces - - (0) - -

-
-
-
-
-
-
- + Network interfaces + + (0) + + +
+
- + + + + Refresh + + + +
+
+ +
+
-
-
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
+
+
+ +
+
+
+
+
+
+ + + + + - - - - - - + + + + +
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
-
- - -
+
+ + - - - - - - - - - - - + - + + + + + + + + +
-
- - - Name - - - - - - + - - MAC - - - - - + - - State - + + + Type + + + +
+
+ + No items found + +
+
+ + + + + + +
+
+
+
+
+
+
+
+

+ Network ports + + (0) - -

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

- Network ports - - (0) - -

+

+ Network settings + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -686,183 +1209,238 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - + + - + - + - + - - - - - + + + + -
- - No items found - -
- - - -
-
- +
+
- - Local port - - - - + + Interface + + + + + - - Local IP address - - - - + - - State + + Netmask + - - - - - - Protocol + + Protocol + - - -
+ + + +
+ + No items found + +
+ + + + +
@@ -870,23 +1448,301 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = `
+
+`; + +exports[`Inventory data Network Network interfaces table A Linux agent should render network interfaces table with correct columns and title. 1`] = ` +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
-

- Network settings - - (0) - -

-
-
-
-
-
-
- + Network interfaces + + (0) + + +
+
- -
-
-
-
-
-
-
-
-
-
-
+
-
- -
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - -
-
- - - - - +
-
- -
-
- - No items found - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

- Packages - - (0) - -

-
-
-
-
-
-
- -
-
- +
+
-
-
-
-
-
+
-
+
-
- -
-
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
-
+
+
+
+
+ +
+
+
+
+
+
+ + + + + - - + +
+
+ + - - - - - - - - - - + + + + + + + + + +
-
- - - Name - - + + State + + + + + + + +
+
+ + No items found + +
+
+ + + + + + +
+
+
+
+
+
+
+
+

+ Network ports + + (0) + +

+
+
+
+
+
+
+ -
+
-
+ + + + +
+
+
+
+
- +
+
+
+
+
+ + + +
+
+
+
+
+
+
- +
+
+ +
+
+
+
+
+
+ + + + - - - - - - - + + + + + + - - No items found - - - - - -
+
- Format + + Local port + - - - - - Location + + Local IP address + - - - - - Description + + Process + - - -
-
+
+ + + PID + + + + + + +
+
+ + No items found + +
+ +
+
+
-
-
-
-
-

- Processes - - (0) - -

+

+ Network settings + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -1852,287 +2692,238 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - - + + - + - + - + - + - + + + + - - - - - - - - - -
-
- - -
+
- - Effective user + + Interface + + - - - - - - PID + + Address + - - - - - - Parent PID + + Netmask + - - - - - - VM size + + Protocol + - - - - - - Priority + + Broadcast + - - - +
-
- - No items found - -
-
+
+ + + + +
@@ -2143,139 +2934,298 @@ exports[`Inventory component A Apple agent should be well rendered. 1`] = `
`; -exports[`Inventory component A Linux agent should be well rendered. 1`] = ` +exports[`Inventory data Network Network interfaces table A Windows agent should render network interfaces table with correct columns and title. 1`] = `
+ +
+
+ + + Cores [object Object] +
+
+
+ +
+
+ + + Memory [object Object] +
+
+
+ +
+
+ + + Arch [object Object] +
+
+
+ +
+
+ + + Operating system [object Object] +
+
+
+ +
+
+ + + CPU [object Object] +
+
+
+ +
+
+ + + Host name [object Object] +
+
+
+ +
+
+ + + Board serial [object Object] +
+
+
+ +
+
+ + + Last scan [object Object] +
-
-
+
-

- Network interfaces - - (0) - -

-
-
-
-
-
-
- + Network interfaces + + (0) + + +
+
- -
-
-
-
-
-
-
-
-
-
-
+
-
- -
-
+ + + + Refresh + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
-
+
+
- - WQL - - - + + + WQL + + + +
+
-
-
-
-
-
+
-
+
-
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + - - - - + + + + + + +
+
+ + No items found + +
+
- + + + +
+
+
+
+
-
- - - - - - - - - - + +
+
+ +
+
+ + + + + + +
+
+
+
-
- - -
-
+
+
+

+ Network ports + + (0) + +

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

- Network ports - - (0) - -

+

+ Network settings + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -2828,214 +4167,238 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - + + - + - + - + - + - + + + + - - - - - - - - - -
-
- +
+
- - Local port - - - - + + Interface + + + + + - - Local IP address - - - - + + Address + + + + - - Process - - - - + + Netmask + + + + - - PID - - - - + - - State + + Broadcast + - - - +
-
- - No items found - -
-
+
+ + + + +
@@ -3043,572 +4406,10845 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = `
+
+`; + +exports[`Inventory data Network Network ports table A Apple agent should render network ports table with correct columns and title. 1`] = ` +
-
-
-
-

- Network settings - - (0) - -

-
-
-
- +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
- + Network interfaces + + (0) + + +
+
- + + + + Refresh + + + +
+
+ +
+
-
-
-
-
-
+
-
+
-
- +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
- - + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network ports + + (0) + +

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

+ Network settings + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Network Network ports table A Linux agent should render network ports table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network interfaces + + (0) + +

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

+ Network ports + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+ + + Local port + + + + + + Local IP address + + + + + + Process + + + + + + PID + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network settings + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Network Network ports table A Windows agent should render network ports table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network interfaces + + (0) + +

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

+ Network ports + + (0) + +

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

+ Network settings + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Network Network settings table A Apple agent should render network settings table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network interfaces + + (0) + +

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

+ Network ports + + (0) + +

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

+ Network settings + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Network Network settings table A Linux agent should render network settings table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network interfaces + + (0) + +

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

+ Network ports + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+ + + Local port + + + + + + Local IP address + + + + + + Process + + + + + + PID + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network settings + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Network Network settings table A Windows agent should render network settings table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Network interfaces + + (0) + +

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

+ Network ports + + (0) + +

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

+ Network settings + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Processes A Apple agent should render processes table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Processes + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Processes A Linux agent should render processes table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Processes + + (0) + +

+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + No items found + +
+
+
+
+
+
+
+
+`; + +exports[`Inventory data Processes A Windows agent should render processes table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ class="euiText euiText--small euiStat__description" + > + +
+
+
+
+
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - -
-
- - - - - - - - - -
-
- - No items found - -
-
-
+
+
-
-
+
-

- Packages - - (0) - -

+

+ Processes + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -3616,239 +15252,288 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - + + + + + - + - + - + - + + + + - - - - - - - - - -
-
-
+
+ + + + + + - - Name + + VM size + - - - - - - - Architecture + + Priority + - - - - - - Version + + NLWP + - - - - - - Vendor + + Command + - - - +
-
- - No items found - -
-
+
+ + + + +
@@ -3856,167 +15541,450 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = `
+
+`; + +exports[`Inventory data Software Packages table A Apple agent should render software table with correct columns and title. 1`] = ` +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
-

- Processes - - (0) - -

+

+ Packages + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
+ +
+
- + +
@@ -4024,408 +15992,240 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - - - - - - - + + - + - + - + - + - + + + + - - - - - - - - - -
-
- - - - - - - - - - - - -
+
- - Argvs + + Name + + - - - - - - VM size + + Version + - - - - - - Size + + Format + - - - - - - Session + + Location + - - - - - - Priority + + Description + - - - +
-
- - No items found - -
-
+
+ + + + +
@@ -4436,283 +16236,447 @@ exports[`Inventory component A Linux agent should be well rendered. 1`] = `
`; -exports[`Inventory component A Windows agent should be well rendered. 1`] = ` +exports[`Inventory data Software Packages table A Linux agent should render software table with correct columns and title. 1`] = `
+ +
+
+ + + Cores [object Object] +
+
+
+ +
+
+ + + Memory [object Object] +
+
+
+ +
+
+ + + Arch [object Object] +
+
+
+ +
+
+ + + Operating system [object Object] +
+
+
+ +
+
+ + + CPU [object Object] +
+
+
+ +
+
+ + + Host name [object Object] +
+
+
+ +
+
+ + + Board serial [object Object] +
+
+
+ +
+
+ + + Last scan [object Object] +
-
-
+
-

- Network interfaces - - (0) - -

+

+ Packages + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -4720,401 +16684,692 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - + + - + - + - + - + - - - - - + + + -
- - No items found - -
- - - -
-
-
+
- - Name + + Name + + - - - - - - - MAC + + Architecture + - - - - - - State + + Version + - - - - - - MTU + + Vendor + - - - - - - Type + + Description + - - -
+ +
+ + No items found + +
+ + + + +
+
+
+`; + +exports[`Inventory data Software Packages table A Windows agent should render software table with correct columns and title. 1`] = ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
-

- Network ports - - (0) - -

+

+ Windows updates + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -5122,375 +17377,308 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - - - - + + - + + + + - - - - - - - - - -
-
- - - Local port - - - - - - Local IP address - - - - - -
+
- - State + + Update code + + - - - +
-
- - No items found - -
-
+
+ + + + +
-
-
-
-
-

- Network settings - - (0) - -

+

+ Packages + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -5498,1095 +17686,976 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - + + - + - + - + - + + + + - - - - - - - - - -
-
-
+
- - Interface + + Name + + - - - - - - - Address + + Architecture + - - - - - - Netmask + + Version + - - - - - - Protocol + + Vendor + - - - +
-
- - No items found - -
-
+
+ + + + +
+
+
+`; + +exports[`Inventory data Software Windows updates table A Windows agent should render software table with correct columns and title. 1`] = ` +
+
- -
+
+
+
+
+
+
+
+ Memory + +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- -
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - -
-
- -
-
- - No items found - -
-
-
+
+
-
-
+
-

- Packages - - (0) - -

+

+ Windows updates + + (0) + +

+
-
-
- -
-
- -
-
-
-
-
-
-
-
-
-
-
-
+
+
+ -
-
-
+ + + + Export formatted + + +
-
-
-
+
-
+
+
- - - Sorting + + WQL + - - -
-
-
-
-
-
- - - - - - + +
-
- - + + + + + + + + +
+
+
+
+
+
+
- -
- + + + + + + + + + + - + + + + - - - - - - - - - -
+
- - Version + + Update code + + - - - +
-
- - No items found - -
-
+ + +
+
-
-
-
-
-

- Processes - - (0) - -

+

+ Packages + + (0) + +

+
-
-
- -
-
- +
+
+ + +
-
-
-
-
+
+
-
-
-
- -
+
+
-
+
+
- - WQL + + WQL + - - + +
@@ -6594,287 +18663,216 @@ exports[`Inventory component A Windows agent should be well rendered. 1`] = `
-
-
-
-
-
+
+
+
-
+
- + +
-
- - - - - - + + - + - + - + - + + + + - - - - - - - - - -
-
- - - - -
+
- - Parent PID + + Name + + - - - - - - VM size + + Architecture + - - - - - - Priority + + Version + - - - - - - NLWP + + Vendor + - - - +
-
- - No items found - -
-
+
+ + + + +
diff --git a/plugins/main/public/components/agents/syscollector/columns/process-columns.ts b/plugins/main/public/components/agents/syscollector/columns/process-columns.ts index a21ee0447d..8cef9f3a39 100644 --- a/plugins/main/public/components/agents/syscollector/columns/process-columns.ts +++ b/plugins/main/public/components/agents/syscollector/columns/process-columns.ts @@ -1,4 +1,10 @@ -import { KeyEquivalence } from "../../../../../common/csv-key-equivalence"; +import { KeyEquivalence } from '../../../../../common/csv-key-equivalence'; + +const mapColumns = ({ field, ...rest }: { field: string; name?: string }) => ({ + ...rest, + field, + name: rest.name || KeyEquivalence[field] || field, +}); const windowsColumns = [ { field: 'name', searchable: true, sortable: true, width: '10%' }, @@ -8,7 +14,8 @@ const windowsColumns = [ { field: 'priority', searchable: true, sortable: true }, { field: 'nlwp', searchable: true, sortable: true }, { field: 'cmd', searchable: true, sortable: true, width: '30%' }, -].map(({field, ...rest}) => ({...rest, field, name: rest.name || KeyEquivalence[field] || field})); +].map(mapColumns); + const linuxColumns = [ { field: 'name', searchable: true, sortable: true, width: '10%' }, { field: 'euser', searchable: true, sortable: true }, @@ -22,7 +29,8 @@ const linuxColumns = [ { field: 'session', searchable: true, sortable: true }, { field: 'nice', searchable: true, sortable: true }, { field: 'state', searchable: true, sortable: true, width: '15%' }, -].map(({field, ...rest}) => ({...rest, field, name: rest.name || KeyEquivalence[field] || field})); +].map(mapColumns); + const macColumns = [ { field: 'name', searchable: true, sortable: true, width: '10%' }, { field: 'euser', searchable: true, sortable: true }, @@ -30,8 +38,7 @@ const macColumns = [ { field: 'ppid', searchable: true, sortable: true }, { field: 'vm_size', searchable: true, sortable: true }, { field: 'nice', searchable: true, sortable: true }, - { field: 'state', searchable: true, sortable: true, width: '15%' }, -].map(({field, ...rest}) => ({...rest, field, name: rest.name || KeyEquivalence[field] || field})); +].map(mapColumns); export const processColumns = { windows: windowsColumns, diff --git a/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx b/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx index 862c4e2cbe..2fde2feae3 100644 --- a/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx +++ b/plugins/main/public/components/agents/syscollector/components/network-interfaces-table.tsx @@ -10,7 +10,7 @@ const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); export const NetworkInterfacesTable = ({ agent }) => { return ( - + (a.label > b.label ? 1 : -1); export const NetworkPortsTable = withSOPlatformGuard( ({ agent, soPlatform }) => { return ( - + (a.label > b.label ? 1 : -1); export const NetworkSettingsTable = ({ agent }) => { return ( - + (a.label > b.label ? 1 : -1); export const PackagesTable = withSOPlatformGuard(({ agent, soPlatform }) => { return ( - + (a.label > b.label ? 1 : -1); export const ProcessesTable = withSOPlatformGuard(({ agent, soPlatform }) => { return ( - + ({ + formatUIDate: jest.fn().mockReturnValue('2022-06-27T16:09:49+00:00'), +})); + +jest.mock('../../../common/hooks/useGenericRequest', () => ({ + useGenericRequest: jest.fn().mockReturnValue({ + isLoading: false, + data: { + hardware: { + cpu: { + name: 'Intel(R) Core(TM) i7-10710U CPU @ 1.10GHz', + cores: 4, + threads: 8, + }, + ram: { + total: '16.0 GB', + }, + }, + os: { + platform: 'windows', + version: '10.0.19045', + }, + }, + error: '', + }), +})); + +describe('Syscollector metrics', () => { + it('should render inventory metrics', () => { + const { container } = render(); + + const inventoryMetrics = container.querySelector( + queryDataTestAttr('syscollector-metrics'), + ); + + expect(inventoryMetrics).toBeTruthy(); + }); + + it('should render syscollector ribbon items', () => { + const { container } = render(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-cores')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-memory')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-arch')), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr('ribbon-item-operating-system'), + ), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-cpu')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-hostname')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-board-serial')), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr('ribbon-item-last-scan')), + ).toBeTruthy(); + + expect( + container.querySelectorAll( + queryDataTestAttr('ribbon-item-', CSS.Attribute.Substring), + ), + ).toHaveLength(8); + }); +}); diff --git a/plugins/main/public/components/agents/syscollector/components/syscollector-metrics.tsx b/plugins/main/public/components/agents/syscollector/components/syscollector-metrics.tsx index be0cf63f59..682e29585b 100644 --- a/plugins/main/public/components/agents/syscollector/components/syscollector-metrics.tsx +++ b/plugins/main/public/components/agents/syscollector/components/syscollector-metrics.tsx @@ -1,15 +1,13 @@ import React, { useState } from 'react'; -import { - EuiPanel, - EuiFlexGroup, - EuiFlexItem, - EuiText, - EuiLoadingSpinner, - EuiIcon, -} from '@elastic/eui'; -import mapValues from 'lodash'; +import { EuiPanel, EuiIcon } from '@elastic/eui'; +import _ from 'lodash'; import { useGenericRequest } from '../../../common/hooks/useGenericRequest'; import { formatUIDate } from '../../../../react-services/time-service'; +import WzRibbon from '../../../common/ribbon/ribbon'; +import { + IRibbonItem, + RibbonItemLabel, +} from '../../../common/ribbon/ribbon-item'; export function InventoryMetrics({ agent }) { const [params, setParams] = useState({}); @@ -31,8 +29,7 @@ export function InventoryMetrics({ agent }) { if ( !syscollector.isLoading && - (mapValues.isEmpty(syscollector.data.hardware) || - mapValues.isEmpty(syscollector.data.os)) + (_.isEmpty(syscollector.data.hardware) || _.isEmpty(syscollector.data.os)) ) { return ( @@ -42,106 +39,72 @@ export function InventoryMetrics({ agent }) { ); } - return ( - - - - - Cores:{' '} - {syscollector.isLoading ? ( - - ) : syscollector.data.hardware.cpu?.cores ? ( - {syscollector.data.hardware.cpu.cores} - ) : ( - - - )} - - - - - Memory:{' '} - {syscollector.isLoading ? ( - - ) : syscollector.data.hardware.ram?.total ? ( - - {(syscollector.data.hardware.ram.total / 1024).toFixed(2)} MB - - ) : ( - - - )} - - - - - Arch:{' '} - {syscollector.isLoading ? ( - - ) : ( - {syscollector.data.os.architecture} - )} - - - - - Operating system:{' '} - {syscollector.isLoading ? ( - - ) : ( - - {syscollector.data.os.os.name} {syscollector.data.os.os.version} - - )} - - - - - CPU:{' '} - {syscollector.isLoading ? ( - - ) : syscollector.data.hardware.cpu?.name ? ( - {syscollector.data.hardware.cpu.name} - ) : ( - - - )} - - - - - Host name:{' '} - {syscollector.isLoading ? ( - - ) : syscollector.data.os.hostname ? ( - {syscollector.data.os.hostname} - ) : ( - - - )} - - - - - Board serial:{' '} - {syscollector.isLoading ? ( - - ) : syscollector.data.hardware.board_serial ? ( - {syscollector.data.hardware.board_serial} - ) : ( - - - )} - - - - - Last scan:{' '} - {syscollector.isLoading ? ( - - ) : ( - - {offsetTimestamp('', syscollector.data.os.scan.time)} - - )} - - - - - ); + const render = () => { + const items: IRibbonItem[] = [ + { + key: 'cores', + label: 'Cores', + value: syscollector?.data?.hardware?.cpu?.cores, + isLoading: syscollector.isLoading, + style: { maxWidth: 100 }, + }, + { + key: 'memory', + label: 'Memory', + value: syscollector?.data?.hardware?.ram?.total + ? `${(syscollector?.data?.hardware?.ram?.total / 1024).toFixed(2)} MB` + : '-', + isLoading: syscollector.isLoading, + style: { maxWidth: 100 }, + }, + { + key: 'arch', + label: 'Arch', + value: syscollector?.data?.os?.architecture, + isLoading: syscollector.isLoading, + style: { maxWidth: 100 }, + }, + { + key: RibbonItemLabel.OPERATING_SYSTEM, + value: syscollector?.data?.os, + label: 'Operating system', + isLoading: syscollector.isLoading, + style: { maxWidth: 200 }, + }, + { + key: 'cpu', + label: 'CPU', + value: syscollector?.data?.hardware?.cpu?.name, + isLoading: syscollector.isLoading, + style: { maxWidth: 250 }, + }, + { + key: 'hostname', + label: 'Host name', + value: syscollector?.data?.os?.hostname, + isLoading: syscollector.isLoading, + style: { maxWidth: 100 }, + }, + { + key: 'board-serial', + label: 'Board serial', + value: syscollector?.data?.hardware?.board_serial, + isLoading: syscollector.isLoading, + style: { maxWidth: 100 }, + }, + { + key: 'last-scan', + label: 'Last scan', + value: syscollector?.data?.os?.scan?.time + ? offsetTimestamp('', syscollector?.data?.os?.scan?.time) + : '-', + isLoading: syscollector.isLoading, + style: { maxWidth: 180 }, + }, + ]; + + return ; + }; + + return render(); } diff --git a/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx index 65e3937cca..34410bc467 100644 --- a/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx +++ b/plugins/main/public/components/agents/syscollector/components/windows-updates-table.tsx @@ -10,7 +10,7 @@ const sortFieldSuggestion = (a, b) => (a.label > b.label ? 1 : -1); export const WindowsUpdatesTable = ({ agent }) => { return ( - + { - it('A Linux agent should be well rendered.', () => { - const agent = { - os: { - uname: - 'Linux |ip-10-0-1-106 |4.9.0-9-amd64 |1 SMP Debian 4.9.168-1+deb9u2 (2019-05-13) |x86_64', - }, - }; - const wrapper = render(); - const networkPortsCard = wrapper.find('.euiFlexItem--flexGrow2').last(); - const networkPortsTitle = networkPortsCard - .find('.euiTitle.euiTitle--small') - .text(); - const networkPortsColumns = networkPortsCard.find('th'); +const TABLE_ID = '__table_7d62db31-1cd0-11ee-8e0c-33242698a3b9'; +const SOFTWARE_PACKAGES = 'Packages'; +const SOFTWARE_WINDOWS_UPDATES = 'Windows updates'; +const NETWORK_PORTS = 'Network ports'; +const NETWORK_INTERFACES = 'Network interfaces'; +const NETWORK_SETTINGS = 'Network settings'; +const PROCESSES = 'Processes'; +const AGENT = { + DEBIAN: { + os: { + uname: + 'Linux |ip-10-0-1-106 |4.9.0-9-amd64 |1 SMP Debian 4.9.168-1+deb9u2 (2019-05-13) |x86_64', + }, + }, + WINDOWS: { + os: { + platform: 'windows', + }, + }, + DARWIN: { + os: { + platform: 'darwin', + }, + }, +} as const; + +const NETWORK_PORTS_COLUMNS = { + LOCAL_PORT: 'Local port', + LOCAL_IP: 'Local IP address', + PROCESS: 'Process', + PID: 'PID', + STATE: 'State', + PROTOCOL: 'Protocol', +} as const; + +const NETWORK_INTERFACES_COLUMNS = { + NAME: 'Name', + MAC: 'MAC', + STATE: 'State', + MTU: 'MTU', + TYPE: 'Type', +} as const; + +const NETWORK_SETTINGS_COLUMNS = { + INTERFACE: 'Interface', + ADDRESS: 'Address', + NETMASK: 'Netmask', + PROTOCOL: 'Protocol', + BROADCAST: 'Broadcast', +} as const; + +const SOFTWARE_PACKAGES_COLUMNS = { + NAME: 'Name', + ARCHITECTURE: 'Architecture', + VENDOR: 'Vendor', + VERSION: 'Version', + FORMAT: 'Format', + LOCATION: 'Location', + DESCRIPTION: 'Description', +} as const; + +const SOFTWARE_WINDOWS_UPDATES_COLUMNS = { + UPDATE_CODE: 'Update code', +} as const; + +const PROCESSES_COLUMNS = { + NAME: 'Name', + EFFECTIVE_USER: 'Effective user', + EFFECTIVE_GROUP: 'Effective group', + PID: 'PID', + PARENT_PID: 'Parent PID', + COMMAND: 'Command', + ARGVS: 'Argvs', + SIZE: 'Size', + VM_SIZE: 'VM size', + SESSION: 'Session', + PRIORITY: 'Priority', + STATE: 'State', + NLWP: 'NLWP', +} as const; + +const shouldRenderTableWithCorrectColumnsAndTitle = ( + dataTestId: string, + title: string, + agentTab: (typeof AgentTabs)[keyof typeof AgentTabs], + agent: (typeof AGENT)[keyof typeof AGENT], + columns: string[], +) => { + const wrapper = render( + , + ); + const visTable = wrapper.find(queryDataTestAttr(dataTestId)).first(); + const visTitle = visTable + .find(queryDataTestAttr('table-wz-api-title')) + .text(); + const visColumns = visTable.find( + queryDataTestAttr('table-with-search-bar') + ' .euiTableHeaderCell', + ); + + const visTables = wrapper.find('table'); + + for (let i = 0; i < visTables.length; i++) { // This is done because the ID of the tables changes at each execution and breaks the snapshot. + visTables[i]['attribs']['id'] = TABLE_ID; + } + + expect(wrapper).toMatchSnapshot(); + expect(visTitle.trim()).toContain(title); + expect(visColumns.length).toEqual(columns.length); + for (let i = 0; i < columns.length; i++) { + expect(visColumns.eq(i).text()).toContain(columns[i]); + } +}; +const shouldRenderNetworkPortsTableWithCorrectColumnsAndTitle = ( + agent: (typeof AGENT)[keyof typeof AGENT], + columns: string[], +) => { + shouldRenderTableWithCorrectColumnsAndTitle( + 'network-ports-table', + NETWORK_PORTS, + AgentTabs.NETWORK, + agent, + columns, + ); +}; + +const shouldRenderNetworkInterfacesTableWithCorrectColumnsAndTitle = ( + agent: (typeof AGENT)[keyof typeof AGENT], + columns: string[], +) => { + shouldRenderTableWithCorrectColumnsAndTitle( + 'network-interfaces-table', + NETWORK_INTERFACES, + AgentTabs.NETWORK, + agent, + columns, + ); +}; + +const shouldRenderNetworkSettingsTableWithCorrectColumnsAndTitle = ( + agent: (typeof AGENT)[keyof typeof AGENT], + columns: string[], +) => { + shouldRenderTableWithCorrectColumnsAndTitle( + 'network-settings-table', + NETWORK_SETTINGS, + AgentTabs.NETWORK, + agent, + columns, + ); +}; + +const shouldRenderSoftwarePackagesTableWithCorrectColumnsAndTitle = ( + agent: (typeof AGENT)[keyof typeof AGENT], + columns: string[], +) => { + shouldRenderTableWithCorrectColumnsAndTitle( + 'software-packages-table', + SOFTWARE_PACKAGES, + AgentTabs.SOFTWARE, + agent, + columns, + ); +}; + +const shouldRenderSoftwareWindowsUpdatesTableWithCorrectColumnsAndTitle = ( + agent: (typeof AGENT)[keyof typeof AGENT], + columns: string[], +) => { + shouldRenderTableWithCorrectColumnsAndTitle( + 'software-windows-updates-table', + SOFTWARE_WINDOWS_UPDATES, + AgentTabs.SOFTWARE, + agent, + columns, + ); +}; + +const shouldRenderProcessesTableWithCorrectColumnsAndTitle = ( + agent: (typeof AGENT)[keyof typeof AGENT], + columns: string[], +) => { + shouldRenderTableWithCorrectColumnsAndTitle( + 'processes-table', + PROCESSES, + AgentTabs.PROCESSES, + agent, + columns, + ); +}; + +function findAgentInfo(wrapper: cheerio.Cheerio): any { + return wrapper.find(queryDataTestAttr('agent-info')).html(); +} + +describe('Inventory data', () => { + describe('Agent info', () => { + it("A Linux agent shouldn't render agent info", () => { + let wrapper = render( + , + ); + + let agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + + wrapper = render( + , + ); + + agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + + wrapper = render( + , + ); + + agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + }); + + it("A Windows agent shouldn't render agent info", () => { + let wrapper = render( + , + ); + + let agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + + wrapper = render( + , + ); + + agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + + wrapper = render( + , + ); + + agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + }); - const tableId = '__table_7d62db31-1cd0-11ee-8e0c-33242698a3b9'; - - const tables = wrapper.find('table'); - - for (let i = 0; i < tables.length; i++) { - tables[i]['attribs']['id'] = tableId; - } - - const columns = [ - 'Local port', - 'Local IP address', - 'Process', - 'PID', - 'State', - 'Protocol', - ]; - - expect(wrapper).toMatchSnapshot(); - expect(networkPortsTitle.trim()).toContain('Network ports'); - expect(networkPortsColumns.length).toEqual(columns.length); - for (let i = 0; i < columns.length; i++) { - expect(networkPortsColumns.eq(i).text()).toContain(columns[i]); - } + it("A Apple agent shouldn't render agent info", () => { + let wrapper = render( + , + ); + + let agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + + wrapper = render( + , + ); + + agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + + wrapper = render( + , + ); + + agentInfo = findAgentInfo(wrapper); + + expect(agentInfo).toBeFalsy(); + }); }); - it('A Windows agent should be well rendered.', () => { - const agent = { - os: { - platform: 'windows', - }, - }; - const wrapper = render(); - const networkPortsCard = wrapper.find('.euiFlexItem--flexGrow2').last(); - const networkPortsTitle = networkPortsCard - .find('.euiTitle.euiTitle--small') - .text(); - const networkPortsColumns = networkPortsCard.find('th'); + describe('Software', () => { + it('should render inventory metrics', () => { + const wrapper = render( + , + ); - // This is done because the ID of the tables changes at each execution and breaks the snapshot. + const inventoryMetrics = wrapper.find( + queryDataTestAttr('syscollector-metrics'), + ); - const tableId = '__table_7d62db31-1cd0-11ee-8e0c-33242698a3b9'; + expect(inventoryMetrics).toBeTruthy(); + }); - const tables = wrapper.find('table'); + describe(SOFTWARE_PACKAGES + ' table', () => { + it('A Linux agent should render software table with correct columns and title.', () => { + const columns = [ + SOFTWARE_PACKAGES_COLUMNS.NAME, + SOFTWARE_PACKAGES_COLUMNS.ARCHITECTURE, + SOFTWARE_PACKAGES_COLUMNS.VERSION, + SOFTWARE_PACKAGES_COLUMNS.VENDOR, + SOFTWARE_PACKAGES_COLUMNS.DESCRIPTION, + ]; - for (let i = 0; i < tables.length; i++) { - tables[i]['attribs']['id'] = tableId; - } + shouldRenderSoftwarePackagesTableWithCorrectColumnsAndTitle( + AGENT.DEBIAN, + columns, + ); + }); + it('A Windows agent should render software table with correct columns and title.', () => { + const columns = [ + SOFTWARE_PACKAGES_COLUMNS.NAME, + SOFTWARE_PACKAGES_COLUMNS.ARCHITECTURE, + SOFTWARE_PACKAGES_COLUMNS.VERSION, + SOFTWARE_PACKAGES_COLUMNS.VENDOR, + ]; - const columns = [ - 'Local port', - 'Local IP address', - 'Process', - 'State', - 'Protocol', - ]; + shouldRenderSoftwarePackagesTableWithCorrectColumnsAndTitle( + AGENT.WINDOWS, + columns, + ); + }); + it('A Apple agent should render software table with correct columns and title.', () => { + const columns = [ + SOFTWARE_PACKAGES_COLUMNS.NAME, + SOFTWARE_PACKAGES_COLUMNS.VERSION, + SOFTWARE_PACKAGES_COLUMNS.FORMAT, + SOFTWARE_PACKAGES_COLUMNS.LOCATION, + SOFTWARE_PACKAGES_COLUMNS.DESCRIPTION, + ]; - expect(wrapper).toMatchSnapshot(); - expect(networkPortsTitle.trim()).toContain('Network ports'); - expect(networkPortsColumns.length).toEqual(columns.length); - for (let i = 0; i < columns.length; i++) { - expect(networkPortsColumns.eq(i).text()).toContain(columns[i]); - } + shouldRenderSoftwarePackagesTableWithCorrectColumnsAndTitle( + AGENT.DARWIN, + columns, + ); + }); + }); + + describe(SOFTWARE_WINDOWS_UPDATES + ' table', () => { + it('A Linux agent should render software table with correct columns and title.', () => { + const wrapper = render( + , + ); + const visTable = wrapper + .find(queryDataTestAttr('software-windows-updates-table]')) + .first() + .html(); + + expect(visTable).toBeFalsy(); + }); + + it('A Windows agent should render software table with correct columns and title.', () => { + const columns = [SOFTWARE_WINDOWS_UPDATES_COLUMNS.UPDATE_CODE]; + + shouldRenderSoftwareWindowsUpdatesTableWithCorrectColumnsAndTitle( + AGENT.WINDOWS, + columns, + ); + }); + + it('A Apple agent should render software table with correct columns and title.', () => { + const wrapper = render( + , + ); + const visTable = wrapper + .find(queryDataTestAttr('software-windows-updates-table')) + .first() + .html(); + + expect(visTable).toBeFalsy(); + }); + }); }); - it('A Apple agent should be well rendered.', () => { - const agent = { - os: { - platform: 'darwin', - }, - }; - const wrapper = render(); - const networkPortsCard = wrapper.find('.euiFlexItem--flexGrow2').last(); - const networkPortsTitle = networkPortsCard - .find('.euiTitle.euiTitle--small') - .text(); - const networkPortsColumns = networkPortsCard.find('th'); + describe('Network', () => { + it('should render inventory metrics', () => { + const wrapper = render( + , + ); - // This is done because the ID of the tables changes at each execution and breaks the snapshot. + const inventoryMetrics = wrapper.find( + queryDataTestAttr('syscollector-metrics'), + ); + + expect(inventoryMetrics).toBeTruthy(); + }); + + describe(NETWORK_SETTINGS + ' table', () => { + it('A Linux agent should render network settings table with correct columns and title.', () => { + const columns = [ + NETWORK_SETTINGS_COLUMNS.INTERFACE, + NETWORK_SETTINGS_COLUMNS.ADDRESS, + NETWORK_SETTINGS_COLUMNS.NETMASK, + NETWORK_SETTINGS_COLUMNS.PROTOCOL, + NETWORK_SETTINGS_COLUMNS.BROADCAST, + ]; + + shouldRenderNetworkSettingsTableWithCorrectColumnsAndTitle( + AGENT.DEBIAN, + columns, + ); + }); + it('A Windows agent should render network settings table with correct columns and title.', () => { + const columns = [ + NETWORK_SETTINGS_COLUMNS.INTERFACE, + NETWORK_SETTINGS_COLUMNS.ADDRESS, + NETWORK_SETTINGS_COLUMNS.NETMASK, + NETWORK_SETTINGS_COLUMNS.PROTOCOL, + NETWORK_SETTINGS_COLUMNS.BROADCAST, + ]; + + shouldRenderNetworkSettingsTableWithCorrectColumnsAndTitle( + AGENT.WINDOWS, + columns, + ); + }); + it('A Apple agent should render network settings table with correct columns and title.', () => { + const columns = [ + NETWORK_SETTINGS_COLUMNS.INTERFACE, + NETWORK_SETTINGS_COLUMNS.ADDRESS, + NETWORK_SETTINGS_COLUMNS.NETMASK, + NETWORK_SETTINGS_COLUMNS.PROTOCOL, + NETWORK_SETTINGS_COLUMNS.BROADCAST, + ]; + + shouldRenderNetworkSettingsTableWithCorrectColumnsAndTitle( + AGENT.DARWIN, + columns, + ); + }); + }); + describe(NETWORK_INTERFACES + ' table', () => { + it('A Linux agent should render network interfaces table with correct columns and title.', () => { + const columns = [ + NETWORK_INTERFACES_COLUMNS.NAME, + NETWORK_INTERFACES_COLUMNS.MAC, + NETWORK_INTERFACES_COLUMNS.STATE, + NETWORK_INTERFACES_COLUMNS.MTU, + NETWORK_INTERFACES_COLUMNS.TYPE, + ]; + + shouldRenderNetworkInterfacesTableWithCorrectColumnsAndTitle( + AGENT.DEBIAN, + columns, + ); + }); + it('A Windows agent should render network interfaces table with correct columns and title.', () => { + const columns = [ + NETWORK_INTERFACES_COLUMNS.NAME, + NETWORK_INTERFACES_COLUMNS.MAC, + NETWORK_INTERFACES_COLUMNS.STATE, + NETWORK_INTERFACES_COLUMNS.MTU, + NETWORK_INTERFACES_COLUMNS.TYPE, + ]; + + shouldRenderNetworkInterfacesTableWithCorrectColumnsAndTitle( + AGENT.WINDOWS, + columns, + ); + }); + it('A Apple agent should render network interfaces table with correct columns and title.', () => { + const columns = [ + NETWORK_INTERFACES_COLUMNS.NAME, + NETWORK_INTERFACES_COLUMNS.MAC, + NETWORK_INTERFACES_COLUMNS.STATE, + NETWORK_INTERFACES_COLUMNS.MTU, + NETWORK_INTERFACES_COLUMNS.TYPE, + ]; + + shouldRenderNetworkInterfacesTableWithCorrectColumnsAndTitle( + AGENT.DARWIN, + columns, + ); + }); + }); + describe(NETWORK_PORTS + ' table', () => { + it('A Linux agent should render network ports table with correct columns and title.', () => { + const columns = [ + NETWORK_PORTS_COLUMNS.LOCAL_PORT, + NETWORK_PORTS_COLUMNS.LOCAL_IP, + NETWORK_PORTS_COLUMNS.PROCESS, + NETWORK_PORTS_COLUMNS.PID, + NETWORK_PORTS_COLUMNS.STATE, + NETWORK_PORTS_COLUMNS.PROTOCOL, + ]; + + shouldRenderNetworkPortsTableWithCorrectColumnsAndTitle( + AGENT.DEBIAN, + columns, + ); + }); + + it('A Windows agent should render network ports table with correct columns and title.', () => { + const columns = [ + NETWORK_PORTS_COLUMNS.LOCAL_PORT, + NETWORK_PORTS_COLUMNS.LOCAL_IP, + NETWORK_PORTS_COLUMNS.PROCESS, + NETWORK_PORTS_COLUMNS.STATE, + NETWORK_PORTS_COLUMNS.PROTOCOL, + ]; + shouldRenderNetworkPortsTableWithCorrectColumnsAndTitle( + AGENT.WINDOWS, + columns, + ); + }); + + it('A Apple agent should render network ports table with correct columns and title.', () => { + const columns = [ + NETWORK_PORTS_COLUMNS.LOCAL_PORT, + NETWORK_PORTS_COLUMNS.LOCAL_IP, + NETWORK_PORTS_COLUMNS.STATE, + NETWORK_PORTS_COLUMNS.PROTOCOL, + ]; + + shouldRenderNetworkPortsTableWithCorrectColumnsAndTitle( + AGENT.DARWIN, + columns, + ); + }); + }); + }); + + describe('Processes', () => { + it('should render inventory metrics', () => { + const wrapper = render( + , + ); + + const inventoryMetrics = wrapper.find( + queryDataTestAttr('syscollector-metrics'), + ); + + expect(inventoryMetrics).toBeTruthy(); + }); + + it('A Linux agent should render processes table with correct columns and title.', () => { + const columns = [ + PROCESSES_COLUMNS.NAME, + PROCESSES_COLUMNS.EFFECTIVE_USER, + PROCESSES_COLUMNS.EFFECTIVE_GROUP, + PROCESSES_COLUMNS.PID, + PROCESSES_COLUMNS.PARENT_PID, + PROCESSES_COLUMNS.COMMAND, + PROCESSES_COLUMNS.ARGVS, + PROCESSES_COLUMNS.VM_SIZE, + PROCESSES_COLUMNS.SIZE, + PROCESSES_COLUMNS.SESSION, + PROCESSES_COLUMNS.PRIORITY, + PROCESSES_COLUMNS.STATE, + ]; - const tableId = '__table_7d62db31-1cd0-11ee-8e0c-33242698a3b9'; + shouldRenderProcessesTableWithCorrectColumnsAndTitle( + AGENT.DEBIAN, + columns, + ); + }); - const tables = wrapper.find('table'); + it('A Windows agent should render processes table with correct columns and title.', () => { + const columns = [ + PROCESSES_COLUMNS.NAME, + PROCESSES_COLUMNS.PID, + PROCESSES_COLUMNS.PARENT_PID, + PROCESSES_COLUMNS.VM_SIZE, + PROCESSES_COLUMNS.PRIORITY, + PROCESSES_COLUMNS.NLWP, + PROCESSES_COLUMNS.COMMAND, + ]; - for (let i = 0; i < tables.length; i++) { - tables[i]['attribs']['id'] = tableId; - } + shouldRenderProcessesTableWithCorrectColumnsAndTitle( + AGENT.WINDOWS, + columns, + ); + }); - const columns = ['Local port', 'Local IP address', 'State', 'Protocol']; + it('A Apple agent should render processes table with correct columns and title.', () => { + const columns = [ + PROCESSES_COLUMNS.NAME, + PROCESSES_COLUMNS.EFFECTIVE_USER, + PROCESSES_COLUMNS.PID, + PROCESSES_COLUMNS.PARENT_PID, + PROCESSES_COLUMNS.VM_SIZE, + PROCESSES_COLUMNS.PRIORITY, + ]; - expect(wrapper).toMatchSnapshot(); - expect(networkPortsTitle.trim()).toContain('Network ports'); - expect(networkPortsColumns.length).toEqual(columns.length); - for (let i = 0; i < columns.length; i++) { - expect(networkPortsColumns.eq(i).text()).toContain(columns[i]); - } + shouldRenderProcessesTableWithCorrectColumnsAndTitle( + AGENT.DARWIN, + columns, + ); + }); }); }); diff --git a/plugins/main/public/components/agents/syscollector/inventory.tsx b/plugins/main/public/components/agents/syscollector/inventory.tsx index 8f5e2413c7..417db55c4c 100644 --- a/plugins/main/public/components/agents/syscollector/inventory.tsx +++ b/plugins/main/public/components/agents/syscollector/inventory.tsx @@ -11,20 +11,22 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; -import { InventoryMetrics } from './components/syscollector-metrics'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiCallOut, + EuiSpacer, + EuiPage, + EuiPageBody, +} from '@elastic/eui'; import { API_NAME_AGENT_STATUS } from '../../../../common/constants'; import { compose } from 'redux'; import { withGuard } from '../../common/hocs'; import { PromptAgentNeverConnected } from '../prompts'; -import { - NetworkInterfacesTable, - NetworkPortsTable, - NetworkSettingsTable, - WindowsUpdatesTable, - ProcessesTable, - PackagesTable, -} from './components'; +import SoftwareTab from './software'; +import NetworkTab from './network'; +import ProcessesTab from './processes'; +import { InventoryMetrics } from './components'; export const SyscollectorInventory = compose( withGuard( @@ -33,7 +35,8 @@ export const SyscollectorInventory = compose( props.agent.status === API_NAME_AGENT_STATUS.NEVER_CONNECTED, PromptAgentNeverConnected, ), -)(function SyscollectorInventory({ agent }) { +)(function SyscollectorInventory(props) { + const { agent, section } = props; let soPlatform; if (agent?.os?.uname?.includes('Linux')) { soPlatform = 'linux'; @@ -48,54 +51,33 @@ export const SyscollectorInventory = compose( } return ( -
- {agent && agent.status === API_NAME_AGENT_STATUS.DISCONNECTED && ( - - - - - - )} - - - - - - - - - - - - - - - - - - - - {agent && agent.os && agent.os.platform === 'windows' && ( - - - + + + {agent?.status === API_NAME_AGENT_STATUS.DISCONNECTED && ( + + + + + )} - - - - - - + - - - - - -
+ + + {section === 'software' && ( + + )} + {section === 'network' && ( + + )} + {section === 'processes' && ( + + )} + + ); }); diff --git a/plugins/main/public/components/agents/syscollector/network.tsx b/plugins/main/public/components/agents/syscollector/network.tsx new file mode 100644 index 0000000000..db99e84f00 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/network.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + NetworkInterfacesTable, + NetworkPortsTable, + NetworkSettingsTable, +} from './components'; +import { Agent } from '../../endpoints-summary/types'; + +interface NetworkProps { + agent: Agent; + soPlatform?: string; +} + +const NetworkTab = ({ agent, soPlatform }: NetworkProps) => { + return ( + + + + + + + + + + + + + + ); +}; + +export default NetworkTab; diff --git a/plugins/main/public/components/agents/syscollector/processes.tsx b/plugins/main/public/components/agents/syscollector/processes.tsx new file mode 100644 index 0000000000..8403be456c --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/processes.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ProcessesTable } from './components'; +import { Agent } from '../../endpoints-summary/types'; + +interface ProcessesProps { + agent: Agent; + soPlatform?: string; +} + +const ProcessesTab = ({ agent, soPlatform }: ProcessesProps) => { + return ( + + + + + + ); +}; + +export default ProcessesTab; diff --git a/plugins/main/public/components/agents/syscollector/software.tsx b/plugins/main/public/components/agents/syscollector/software.tsx new file mode 100644 index 0000000000..a8c8f1db27 --- /dev/null +++ b/plugins/main/public/components/agents/syscollector/software.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Agent } from '../../endpoints-summary/types'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { PackagesTable, WindowsUpdatesTable } from './components'; + +interface SoftwareProps { + agent: Agent; + soPlatform?: string; +} + +const SoftwareTab = ({ agent, soPlatform }: SoftwareProps) => { + return ( + + {agent?.os?.platform === 'windows' && ( + + + + )} + + + + + ); +}; + +export default SoftwareTab; diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx index 8eb829a886..7eb175314c 100644 --- a/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.test.tsx @@ -61,7 +61,7 @@ describe('cell-filter-actions', () => { fireEvent.click(component); expect(onFilter).toHaveBeenCalledTimes(1); - expect(onFilter).toHaveBeenCalledWith(TEST_COLUMN_ID, TEST_VALUE, 'is'); + expect(onFilter).toHaveBeenCalledWith(TEST_COLUMN_ID, 'is', TEST_VALUE); }); }); @@ -102,8 +102,8 @@ describe('cell-filter-actions', () => { expect(onFilter).toHaveBeenCalledTimes(1); expect(onFilter).toHaveBeenCalledWith( TEST_COLUMN_ID, - TEST_VALUE, 'is not', + TEST_VALUE, ); }); }); diff --git a/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx index 6f9007dffc..cab188384b 100644 --- a/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx +++ b/plugins/main/public/components/common/data-grid/cell-filter-actions.tsx @@ -15,14 +15,14 @@ export const filterIsAction = ( rows: any[], pageSize: number, onFilter: ( - columndId: string, - value: any, + field: string, operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, + value?: any, ) => void, ) => { return ({ rowIndex, - columnId, + columnId: field, Component, }: EuiDataGridColumnCellActionProps) => { const filterForValueText = i18n.translate('discover.filterForValue', { @@ -30,7 +30,7 @@ export const filterIsAction = ( }); const filterForValueLabel = i18n.translate('discover.filterForValueLabel', { defaultMessage: 'Filter for value: {value}', - values: { value: columnId }, + values: { value: field }, }); const handleClick = () => { @@ -38,7 +38,7 @@ export const filterIsAction = ( const flattened = indexPattern.flattenHit(row); if (flattened) { - onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS); + onFilter(field, FILTER_OPERATOR.IS, flattened[field]); } }; @@ -61,18 +61,22 @@ export const filterIsNotAction = rows: any[], pageSize: number, onFilter: ( - columndId: string, - value: any, + field: string, operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, + value?: any, ) => void, ) => - ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => { + ({ + rowIndex, + columnId: field, + Component, + }: EuiDataGridColumnCellActionProps) => { const filterOutValueText = i18n.translate('discover.filterOutValue', { defaultMessage: 'Filter out value', }); const filterOutValueLabel = i18n.translate('discover.filterOutValueLabel', { defaultMessage: 'Filter out value: {value}', - values: { value: columnId }, + values: { value: field }, }); const handleClick = () => { @@ -80,7 +84,7 @@ export const filterIsNotAction = const flattened = indexPattern.flattenHit(row); if (flattened) { - onFilter(columnId, flattened[columnId], FILTER_OPERATOR.IS_NOT); + onFilter(field, FILTER_OPERATOR.IS_NOT, flattened[field]); } }; @@ -103,9 +107,9 @@ export function cellFilterActions( rows: any[], pageSize: number, onFilter: ( - columndId: string, - value: any, + field: string, operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, + value: any, ) => void, ) { if (!field.filterable) return; diff --git a/plugins/main/public/components/common/data-grid/data-grid-service.ts b/plugins/main/public/components/common/data-grid/data-grid-service.ts index bdd6cace7e..b209d05bdf 100644 --- a/plugins/main/public/components/common/data-grid/data-grid-service.ts +++ b/plugins/main/public/components/common/data-grid/data-grid-service.ts @@ -10,10 +10,7 @@ import { export const MAX_ENTRIES_PER_QUERY = 10000; import { tDataGridColumn } from './use-data-grid'; import { cellFilterActions } from './cell-filter-actions'; -import { - FILTER_OPERATOR, - PatternDataSourceFilterManager, -} from '../data-source/pattern/pattern-data-source-filter-manager'; +import { onFilterCellActions } from './filter-cell-actions'; type ParseData = | { @@ -192,26 +189,6 @@ export const exportSearchToCSV = async ( } }; -const onFilterCellActions = ( - indexPatternId: string, - filters: Filter[], - setFilters: (filters: Filter[]) => void, -) => { - return ( - columndId: string, - value: any, - operation: FILTER_OPERATOR.IS | FILTER_OPERATOR.IS_NOT, - ) => { - const newFilter = PatternDataSourceFilterManager.createFilter( - operation, - columndId, - value, - indexPatternId, - ); - setFilters([...filters, newFilter]); - }; -}; - const mapToDataGridColumn = ( field: IFieldType, indexPattern: IndexPattern, diff --git a/plugins/main/public/components/common/data-grid/filter-cell-actions.test.ts b/plugins/main/public/components/common/data-grid/filter-cell-actions.test.ts new file mode 100644 index 0000000000..a6dae0615f --- /dev/null +++ b/plugins/main/public/components/common/data-grid/filter-cell-actions.test.ts @@ -0,0 +1,265 @@ +import { FilterStateStore } from '../../../../common/constants'; +import { onFilterCellActions } from './filter-cell-actions'; +import { FILTER_OPERATOR } from '../data-source'; + +const KEY = 'test-key'; +const INDEX_PATTERN_ID = 'index-pattern-test'; + +const buildMatchFilter = ( + key: string, + operation: string, + value: string | string[] | any, +) => { + return { + meta: { + alias: null, + controlledBy: undefined, + disabled: false, + key: key, + params: value, + value: Array.isArray(value) ? value.join(', ') : value, + negate: operation.includes('not'), + type: Array.isArray(value) ? 'phrases' : 'phrase', + index: INDEX_PATTERN_ID, + }, + query: { match_phrase: { [key]: { query: value } } }, + $state: { store: FilterStateStore.APP_STATE }, + }; +}; + +const buildExistsFilter = (key: string, operation: string) => { + return { + exists: { field: key }, + meta: { + alias: null, + controlledBy: undefined, + disabled: false, + key: key, + value: 'exists', + negate: operation.includes('not'), + type: 'exists', + index: INDEX_PATTERN_ID, + }, + $state: { store: FilterStateStore.APP_STATE }, + }; +}; + +describe('onFilterCellActions', () => { + let setFilters: jest.Mock; + + beforeEach(() => { + setFilters = jest.fn(); + }); + + it('should add single filter with given key and number value (3)', () => { + const value = 3; + const operation = FILTER_OPERATOR.IS; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with is not operator for given key and number value (3)', () => { + const value = 3; + const operation = FILTER_OPERATOR.IS_NOT; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with given key and string value (19003)', () => { + const value = '19003'; + const operation = FILTER_OPERATOR.IS; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with is not operator for given key and string value (19003)', () => { + const value = '19003'; + const operation = FILTER_OPERATOR.IS_NOT; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with given key and boolean value (true)', () => { + const value = true; + const operation = FILTER_OPERATOR.IS; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with is not operator for given key and boolean value (true)', () => { + const value = true; + const operation = FILTER_OPERATOR.IS_NOT; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with given key and date value (2024-10-19T18:44:40.487Z)', () => { + const value = '2024-10-19T18:44:40.487Z'; + const operation = FILTER_OPERATOR.IS; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter is not operator for given key and date value (2024-10-19T18:44:40.487Z)', () => { + const value = '2024-10-19T18:44:40.487Z'; + const operation = FILTER_OPERATOR.IS_NOT; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with given key and ip value (10.0.2.2)', () => { + const value = '10.0.2.2'; + const operation = FILTER_OPERATOR.IS; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add single filter with is not operator for given key and ip value (10.0.2.2)', () => { + const value = '10.0.2.2'; + const operation = FILTER_OPERATOR.IS_NOT; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + value, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, value), + ]); + }); + + it('should add two filters with given key and values (group1, group2) respectively', () => { + const values = ['group1', 'group2']; + const operation = FILTER_OPERATOR.IS; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + values, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, values[0]), + buildMatchFilter(KEY, operation, values[1]), + ]); + }); + + it('should add two filters with is not operator for given key and values (group1, group2) respectively', () => { + const values = ['group1', 'group2']; + const operation = FILTER_OPERATOR.IS_NOT; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + values, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildMatchFilter(KEY, operation, values[0]), + buildMatchFilter(KEY, operation, values[1]), + ]); + }); + + it('should add single filter with given key and undefined value', () => { + const values = undefined; + const operation = FILTER_OPERATOR.IS; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + values, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildExistsFilter(KEY, FILTER_OPERATOR.DOES_NOT_EXISTS), + ]); + }); + + it('should add single filter with is not operator for given key and undefined value', () => { + const values = undefined; + const operation = FILTER_OPERATOR.IS_NOT; + + onFilterCellActions(INDEX_PATTERN_ID, [], setFilters)( + KEY, + operation, + values, + ); + + expect(setFilters).toHaveBeenCalledWith([ + buildExistsFilter(KEY, FILTER_OPERATOR.EXISTS), + ]); + }); +}); diff --git a/plugins/main/public/components/common/data-grid/filter-cell-actions.ts b/plugins/main/public/components/common/data-grid/filter-cell-actions.ts new file mode 100644 index 0000000000..8b108e67a7 --- /dev/null +++ b/plugins/main/public/components/common/data-grid/filter-cell-actions.ts @@ -0,0 +1,57 @@ +import { + FILTER_OPERATOR, + PatternDataSourceFilterManager, +} from '../data-source/pattern/pattern-data-source-filter-manager'; +import { Filter } from '../../../../../../src/plugins/data/common'; +import { isNullish } from '../util'; + +export const onFilterCellActions = ( + indexPatternId: string, + filters: Filter[], + setFilters: (filters: Filter[]) => void, +) => { + return ( + field: string, + operation: + | FILTER_OPERATOR.EXISTS + | FILTER_OPERATOR.IS + | FILTER_OPERATOR.IS_NOT, + values?: boolean | number | string | (boolean | number | string)[], + ) => { + // https://github.com/opensearch-project/OpenSearch-Dashboards/blob/4e34a7a5141d089f6c341a535be5a7ba2737d965/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts#L89 + const negated = [FILTER_OPERATOR.IS_NOT].includes(operation); + let _operation: FILTER_OPERATOR = operation; + if (isNullish(values) && ![FILTER_OPERATOR.EXISTS].includes(operation)) { + if (negated) { + _operation = FILTER_OPERATOR.EXISTS; + } else { + _operation = FILTER_OPERATOR.DOES_NOT_EXISTS; + } + } + + const newFilters: Filter[] = []; + if (isNullish(values)) { + newFilters.push( + PatternDataSourceFilterManager.createFilter( + _operation, + field, + values, + indexPatternId, + ), + ); + } else { + values = Array.isArray(values) ? values : [values]; + values.forEach(item => { + newFilters.push( + PatternDataSourceFilterManager.createFilter( + _operation, + field, + item, + indexPatternId, + ), + ); + }); + } + setFilters([...filters, ...newFilters]); + }; +}; diff --git a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts index 8e4781c360..40bd4c2cd6 100644 --- a/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts +++ b/plugins/main/public/components/common/data-source/pattern/pattern-data-source-filter-manager.ts @@ -10,11 +10,10 @@ import { tDataSource, tFilterManager, } from '../index'; -import { - DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, - AUTHORIZED_AGENTS, -} from '../../../../../common/constants'; +import { DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER } from '../../../../../common/constants'; import { PinnedAgentManager } from '../../../wz-agent-selector/wz-agent-selector-service'; +import { FilterStateStore } from '../../../../../common/constants'; + const MANAGER_AGENT_ID = '000'; const AGENT_ID_KEY = 'agent.id'; @@ -36,7 +35,7 @@ export function getFilterExcludeManager(indexPatternId: string) { controlledBy: DATA_SOURCE_FILTER_CONTROLLED_EXCLUDE_SERVER, }, query: { match_phrase: { [AGENT_ID_KEY]: MANAGER_AGENT_ID } }, - $state: { store: 'appState' }, + $state: { store: FilterStateStore.APP_STATE }, }; } @@ -278,7 +277,7 @@ export class PatternDataSourceFilterManager }; //@ts-ignore managerFilter.$state = { - store: 'appState', + store: FilterStateStore.APP_STATE, }; //@ts-ignore return [managerFilter] as tFilter[]; @@ -382,7 +381,7 @@ export class PatternDataSourceFilterManager controlledBy, }, exists: { field: key }, - $state: { store: 'appState' }, + $state: { store: FilterStateStore.APP_STATE }, }; case FILTER_OPERATOR.IS_ONE_OF: case FILTER_OPERATOR.IS_NOT_ONE_OF: @@ -429,7 +428,7 @@ export class PatternDataSourceFilterManager lte: value[1] || NaN, }, }, - $state: { store: 'appState' }, + $state: { store: FilterStateStore.APP_STATE }, }; default: throw new Error('Invalid filter type'); @@ -484,7 +483,7 @@ export class PatternDataSourceFilterManager controlledBy, }, ...query, - $state: { store: 'appState' }, + $state: { store: FilterStateStore.APP_STATE }, }; } diff --git a/plugins/main/public/components/common/doc-viewer/doc-viewer.scss b/plugins/main/public/components/common/doc-viewer/doc-viewer.scss new file mode 100644 index 0000000000..7f46b8f7c9 --- /dev/null +++ b/plugins/main/public/components/common/doc-viewer/doc-viewer.scss @@ -0,0 +1,94 @@ +.wzDocViewerTable { + pre, + .wzDocViewer__value { + display: inline-block; + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; + color: $euiColorFullShade; + vertical-align: top; + padding-top: 2px; + } + + .wzDocViewer__field { + padding-top: 8px; + } + + .dscFieldName { + color: $euiColorDarkShade; + } + + td, + pre { + font-family: $euiCodeFontFamily; + } + + tr { + position: relative; + } + + tr:first-child td { + border-top-color: transparent; + } + + tr:hover { + .wzDocViewer__buttons { + display: flex; + gap: 2px; + position: absolute; + right: 0; + top: 0; + transform: translateY(calc(50% - 8px)); + + > span { + z-index: 2; + } + + .wzDocViewer__actionButton { + opacity: 1; + } + + &::before { + content: ''; + position: absolute; + display: block; + right: 0; + top: 0; + height: 100%; + width: 100%; + background-image: linear-gradient( + to right, + transparent 0, + aliceblue 4px + ); + z-index: 1; + } + } + } +} + +.wzDocViewer__buttons, +.wzDocViewer__field { + white-space: nowrap; +} + +.wzDocViewer__buttons { + display: none; +} + +.wzDocViewer__field { + width: 160px; + white-space: break-spaces; +} + +.wzDocViewer__actionButton { + opacity: 0; + + &:hover { + opacity: 1; + } +} + +.wzDocViewer__warning { + margin-right: $euiSizeS; +} diff --git a/plugins/main/public/components/common/doc-viewer/doc-viewer.tsx b/plugins/main/public/components/common/doc-viewer/doc-viewer.tsx index f0249a1eaf..9ebc58e339 100644 --- a/plugins/main/public/components/common/doc-viewer/doc-viewer.tsx +++ b/plugins/main/public/components/common/doc-viewer/doc-viewer.tsx @@ -4,6 +4,13 @@ import { escapeRegExp } from 'lodash'; import { i18n } from '@osd/i18n'; import { FieldIcon } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FILTER_OPERATOR } from '../data-source'; +import { DocViewTableRowBtnFilterAdd } from './table-row-btn-filter-add'; +import { DocViewTableRowBtnFilterRemove } from './table-row-btn-filter-remove'; +import { DocViewTableRowBtnFilterExists } from './table-row-btn-filter-exists'; +import './doc-viewer.scss'; +import { onFilterCellActions } from '../data-grid/filter-cell-actions'; +import { Filter } from '../../../../../../src/plugins/data/common'; const COLLAPSE_LINE_LENGTH = 350; const DOT_PREFIX_RE = /(.).+?\./g; @@ -15,6 +22,9 @@ export type tDocViewerProps = { mapping: any; indexPattern: any; docJSON: any; + filters: Filter[]; + setFilters: (filters: Filter[]) => void; + onFilter?: () => void; }; /** @@ -82,13 +92,39 @@ const DocViewer = (props: tDocViewerProps) => { const [fieldRowOpen, setFieldRowOpen] = useState( {} as Record, ); - const { flattened, formatted, mapping, indexPattern, renderFields, docJSON } = - props; + const { + flattened, + formatted, + mapping, + indexPattern, + renderFields, + docJSON, + filters, + setFilters, + onFilter: onClose, + } = props; + + const onFilter = ( + field: string, + operation: + | FILTER_OPERATOR.IS + | FILTER_OPERATOR.IS_NOT + | FILTER_OPERATOR.EXISTS, + value?: string | string[], + ) => { + const _onFilter = onFilterCellActions( + indexPattern?.id, + filters, + setFilters, + ); + _onFilter(field, operation, value); + onClose?.(); + }; return ( <> {flattened && ( - +
{Object.keys(flattened) .sort() @@ -99,7 +135,7 @@ const DocViewer = (props: tDocViewerProps) => { const isCollapsed = isCollapsible && !fieldRowOpen[field]; const valueClassName = classNames({ // eslint-disable-next-line @typescript-eslint/naming-convention - osdDocViewer__value: true, + wzDocViewer__value: true, 'truncate-by-height': isCollapsible && isCollapsed, }); const isNestedField = @@ -123,7 +159,7 @@ const DocViewer = (props: tDocViewerProps) => { return ( -
+ { +
+ + onFilter( + field, + FILTER_OPERATOR.IS, + flattened[field], + ) + } + /> + + onFilter( + field, + FILTER_OPERATOR.IS_NOT, + flattened[field], + ) + } + /> + + onFilter(field, FILTER_OPERATOR.EXISTS) + } + scripted={fieldMapping && fieldMapping.scripted} + /> +
{renderFields && renderFields?.find( (field: string) => field?.id === displayName, diff --git a/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-add.tsx b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-add.tsx new file mode 100644 index 0000000000..cf405e126d --- /dev/null +++ b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-add.tsx @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@osd/i18n/react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import styles from './doc-viewer.scss'; + +export interface Props { + onClick: () => void; + disabled: boolean; +} + +export function DocViewTableRowBtnFilterAdd({ + onClick, + disabled = false, +}: Props) { + const tooltipContent = disabled ? ( + + ) : ( + + ); + + return ( + + + + ); +} diff --git a/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-exists.tsx b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-exists.tsx new file mode 100644 index 0000000000..033ee42005 --- /dev/null +++ b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-exists.tsx @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@osd/i18n/react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; + +export interface Props { + onClick: () => void; + disabled?: boolean; + scripted?: boolean; +} + +export function DocViewTableRowBtnFilterExists({ + onClick, + disabled = false, + scripted = false, +}: Props) { + const tooltipContent = disabled ? ( + scripted ? ( + + ) : ( + + ) + ) : ( + + ); + + return ( + + + + ); +} diff --git a/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-remove.tsx b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-remove.tsx new file mode 100644 index 0000000000..22318d3b1b --- /dev/null +++ b/plugins/main/public/components/common/doc-viewer/table-row-btn-filter-remove.tsx @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { FormattedMessage } from '@osd/i18n/react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; + +export interface Props { + onClick: () => void; + disabled?: boolean; +} + +export function DocViewTableRowBtnFilterRemove({ + onClick, + disabled = false, +}: Props) { + const tooltipContent = disabled ? ( + + ) : ( + + ); + + return ( + + + + ); +} diff --git a/plugins/main/public/components/common/hooks/use_async_action.ts b/plugins/main/public/components/common/hooks/use_async_action.ts index 24ab181926..a98495fa9c 100644 --- a/plugins/main/public/components/common/hooks/use_async_action.ts +++ b/plugins/main/public/components/common/hooks/use_async_action.ts @@ -11,24 +11,27 @@ */ import { useCallback, useState } from 'react'; -export function useAsyncAction(action, dependencies = []){ +export function useAsyncAction( + action: (...params: any[]) => any, + dependencies: any[] = [], +) { const [running, setRunning] = useState(false); const [data, setData] = useState(null); const [error, setError] = useState(null); const run = useCallback(async (...params) => { - try{ + try { setRunning(true); setError(null); setData(null); const data = await action(...params); setData(data); - }catch(error){ + } catch (error) { setError(error); - }finally{ + } finally { setRunning(false); - }; + } }, dependencies); return { data, error, running, run }; -} \ No newline at end of file +} diff --git a/plugins/main/public/components/common/modules/main-agent.test.tsx b/plugins/main/public/components/common/modules/main-agent.test.tsx new file mode 100644 index 0000000000..02ba68cd67 --- /dev/null +++ b/plugins/main/public/components/common/modules/main-agent.test.tsx @@ -0,0 +1,359 @@ +import React from 'react'; +import { MainModuleAgent } from './main-agent'; +import { AgentTabs } from '../../endpoints-summary/agent/agent-tabs'; +import { fireEvent, render } from '@testing-library/react'; +import { queryDataTestAttr } from '../../../../test/public/query-attr'; + +const GENERATE_REPORT_BUTTON = 'generate-report-button'; +const ARIA_SELECTED = '[aria-selected="true"]'; + +const REPORT_TAB = { + STATS: 'agent-tab-stats', + CONFIGURATION: 'agent-tab-configuration', + SOFTWARE: 'agent-tab-software', + NETWORK: 'agent-tab-network', + PROCESSES: 'agent-tab-processes', +}; + +jest.mock('../../../react-services/reporting', () => ({ + ReportingService: { + startVis2Png: jest.fn(), + startConfigReport: jest.fn(), + }, +})); + +jest.mock('../data-source', () => ({ + useDataSource: jest.fn().mockImplementation(() => ({ + dataSource: {}, + fetchFilters: {}, + isLoading: false, + })), + AlertsDataSourceRepository: jest.fn(), + AlertsDataSource: jest.fn(), + __esModule: true, +})); + +describe('Main Agent', () => { + let switchTab: jest.Mock; + + beforeEach(() => { + switchTab = jest.fn(); + }); + + describe('Agent tabs', () => { + it('should render agent tab overview when section is stats', () => { + const { container } = render( + , + ); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.STATS) + ARIA_SELECTED, + ), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)), + ).toBeFalsy(); + }); + + it('should render agent tab overview when section is configuration', () => { + const { container } = render( + , + ); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.CONFIGURATION) + ARIA_SELECTED, + ), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)), + ).toBeFalsy(); + }); + + it('should render agent tab overview when section is software', () => { + const { container } = render( + , + ); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.SOFTWARE) + ARIA_SELECTED, + ), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.NETWORK) + ARIA_SELECTED, + ), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.PROCESSES) + ARIA_SELECTED, + ), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)), + ).toBeFalsy(); + }); + + it('should render agent tab overview when section is network', () => { + const { container } = render( + , + ); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.SOFTWARE) + ARIA_SELECTED, + ), + ).toBeFalsy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.NETWORK) + ARIA_SELECTED, + ), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.PROCESSES)), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.PROCESSES) + ARIA_SELECTED, + ), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)), + ).toBeFalsy(); + }); + + it('should render agent tab overview when section is processes', () => { + const { container } = render( + , + ); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.SOFTWARE)), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.SOFTWARE) + ARIA_SELECTED, + ), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.NETWORK)), + ).toBeTruthy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.NETWORK) + ARIA_SELECTED, + ), + ).toBeFalsy(); + + expect( + container.querySelector( + queryDataTestAttr(REPORT_TAB.PROCESSES) + ARIA_SELECTED, + ), + ).toBeTruthy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.CONFIGURATION)), + ).toBeFalsy(); + + expect( + container.querySelector(queryDataTestAttr(REPORT_TAB.STATS)), + ).toBeFalsy(); + }); + + it('should be call switchTab when click on tab', () => { + const { container } = render( + , + ); + + fireEvent.click( + container.querySelector( + queryDataTestAttr(REPORT_TAB.SOFTWARE), + ) as Element, + ); + + expect(switchTab).toHaveBeenCalledTimes(1); + expect(switchTab).toHaveBeenCalledWith(AgentTabs.SOFTWARE); + + fireEvent.click( + container.querySelector( + queryDataTestAttr(REPORT_TAB.NETWORK), + ) as Element, + ); + + expect(switchTab).toHaveBeenCalledTimes(2); + expect(switchTab).toHaveBeenCalledWith(AgentTabs.NETWORK); + + fireEvent.click( + container.querySelector( + queryDataTestAttr(REPORT_TAB.PROCESSES), + ) as Element, + ); + + expect(switchTab).toHaveBeenCalledTimes(3); + expect(switchTab).toHaveBeenCalledWith(AgentTabs.PROCESSES); + }); + }); + + describe('Generate report button', () => { + it("shouldn't render generate report button when section is stats", () => { + const { container } = render( + , + ); + + const generateReportButton = container.querySelector( + queryDataTestAttr(GENERATE_REPORT_BUTTON), + ); + + expect(generateReportButton).toBeFalsy(); + }); + + it("shouldn't render generate report button when section is configuration", () => { + const { container } = render( + , + ); + + const generateReportButton = container.querySelector( + queryDataTestAttr(GENERATE_REPORT_BUTTON), + ); + + expect(generateReportButton).toBeFalsy(); + }); + + it('should render generate report button when section is software', () => { + const { container } = render( + , + ); + + const generateReportButton = container.querySelector( + queryDataTestAttr(GENERATE_REPORT_BUTTON), + ); + + expect(generateReportButton).toBeTruthy(); + }); + + it('should render generate report button when section is network', () => { + const { container } = render( + , + ); + + const generateReportButton = container.querySelector( + queryDataTestAttr(GENERATE_REPORT_BUTTON), + ); + + expect(generateReportButton).toBeTruthy(); + }); + + it('should render generate report button when section is processes', () => { + const { container } = render( + , + ); + + const generateReportButton = container.querySelector( + queryDataTestAttr(GENERATE_REPORT_BUTTON), + ); + + expect(generateReportButton).toBeTruthy(); + }); + }); +}); diff --git a/plugins/main/public/components/common/modules/main-agent.tsx b/plugins/main/public/components/common/modules/main-agent.tsx index b7d477038f..74ebf831e0 100644 --- a/plugins/main/public/components/common/modules/main-agent.tsx +++ b/plugins/main/public/components/common/modules/main-agent.tsx @@ -14,20 +14,13 @@ import React, { Component, Fragment } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiTitle, EuiButtonEmpty, + EuiTabs, + EuiTab, } from '@elastic/eui'; -import { euiThemeVars } from '@osd/ui-shared-deps/theme'; import '../../common/modules/module.scss'; import store from '../../../redux/store'; -import { FilterHandler } from '../../../utils/filter-handler'; -import { AppState } from '../../../react-services/app-state'; import { ReportingService } from '../../../react-services/reporting'; -import { WAZUH_MODULES } from '../../../../common/wazuh-modules'; -import { AgentInfo } from '../../common/welcome/agents-info'; -import { compose } from 'redux'; -import { withGlobalBreadcrumb } from '../hocs'; -import { endpointSummary } from '../../../utils/applications'; import { AlertsDataSource, AlertsDataSourceRepository, @@ -37,140 +30,103 @@ import { useDataSource, } from '../data-source'; import { useAsyncAction } from '../hooks'; -import NavigationService from '../../../react-services/navigation-service'; +import { toTitleCase } from '../util/change-case'; +import clsx from 'clsx'; +import { AgentTabs } from '../../endpoints-summary/agent/agent-tabs'; +import { Agent } from '../../endpoints-summary/types'; export class MainModuleAgent extends Component { props!: { - [key: string]: any; + agent: Agent; + section: string; + switchTab?: (tab: string) => void; + selectView?: boolean; + tabs?: any[]; + renderTabs?: () => JSX.Element; + agentsSelectionProps?: any; }; - state: { - selectView: Boolean; - loadingReport: Boolean; - switchModule: Boolean; - showAgentInfo: Boolean; - }; - reportingService: ReportingService; - filterHandler: FilterHandler; - constructor(props) { - super(props); - this.reportingService = new ReportingService(); - this.filterHandler = new FilterHandler(AppState.getCurrentPattern()); - this.state = { - selectView: false, - loadingReport: false, - switchModule: false, - showAgentInfo: false, - }; - } + inventoryTabs = [AgentTabs.SOFTWARE, AgentTabs.NETWORK, AgentTabs.PROCESSES]; renderTitle() { + const { agent, section, switchTab } = this.props; return ( - - - - - - -

- { - NavigationService.getInstance().navigate( - `/agents?tab=welcome&agent=${this.props.agent.id}`, - ); - }} - > - -  {this.props.agent.name}    - - -

-
-
-
- - {this.props.section === 'syscollector' && ( - - - + + + + {this.inventoryTabs.includes(section) ? ( + <> + {this.inventoryTabs.map(tab => ( + switchTab?.(tab)} + > + {toTitleCase(tab)} + + ))} + + ) : ( + + {toTitleCase(section)} + )} - + + + {[AgentTabs.SOFTWARE, AgentTabs.NETWORK, AgentTabs.PROCESSES].includes( + section, + ) && ( + + + + )}
); } render() { const { agent, section, selectView } = this.props; - const ModuleTabView = (this.props.tabs || []).find( - tab => tab.id === selectView, - ); + const ModuleTabView = this.props.tabs?.find(tab => tab.id === selectView); + + const hasTabs = this.props.tabs?.length; + return ( -
- {agent && agent.os && ( +
+ {agent?.os && (
{this.renderTitle()}
-
- {this.state.showAgentInfo && ( -
- -
- )} - {this.props.tabs && this.props.tabs.length && ( +
+ {hasTabs && (
- {this.props.renderTabs()} + {this.props.renderTabs?.()} - {ModuleTabView && - ModuleTabView.buttons && - ModuleTabView.buttons.map( - (ModuleViewButton, index) => - typeof ModuleViewButton !== 'string' ? ( - - - - ) : null, - )} + {ModuleTabView?.buttons?.map( + (ModuleViewButton, index) => + typeof ModuleViewButton !== 'string' ? ( + + + + ) : null, + )} @@ -178,9 +134,8 @@ export class MainModuleAgent extends Component { )}
- {!['syscollector', 'configuration'].includes(section) && - ModuleTabView && - ModuleTabView.component && ( + {[AgentTabs.STATS].includes(section) && + ModuleTabView?.component && ( )} @@ -190,41 +145,6 @@ export class MainModuleAgent extends Component { } } -export default compose( - withGlobalBreadcrumb(({ agent, section }) => { - if (section === 'welcome') { - return [ - { - text: endpointSummary.breadcrumbLabel, - href: NavigationService.getInstance().getUrlForApp( - endpointSummary.id, - { - path: `#/agents-preview`, - }, - ), - }, - { text: agent.id }, - ]; - } else { - return [ - { - text: endpointSummary.breadcrumbLabel, - href: NavigationService.getInstance().getUrlForApp( - endpointSummary.id, - { - path: `#/agents-preview`, - }, - ), - }, - { agent: agent }, - { - text: WAZUH_MODULES[section].title, - }, - ]; - } - }), -)(MainModuleAgent); - export class AgentInventoryDataSource extends AlertsDataSource { constructor(id: string, title: string) { super(id, title); @@ -238,7 +158,7 @@ export class AgentInventoryDataSource extends AlertsDataSource { } } -const GenerateSyscollectorReportButton = ({ agent }) => { +const GenerateReportButton = ({ agent }: { agent: Agent }) => { const { dataSource, fetchFilters, @@ -254,7 +174,7 @@ const GenerateSyscollectorReportButton = ({ agent }) => { (agent || store.getState().appStateReducers.currentAgentData || {}).id || false; await reportingService.startVis2Png('syscollector', agentID, { - indexPattern: dataSource.indexPattern, + indexPattern: dataSource?.indexPattern, query: { query: '', language: 'kuery' }, filters: fetchFilters, time: { @@ -266,6 +186,7 @@ const GenerateSyscollectorReportButton = ({ agent }) => { return ( {ModuleTabView && diff --git a/plugins/main/public/components/common/modules/module.scss b/plugins/main/public/components/common/modules/module.scss index 8182b3d601..dbbd4491f1 100644 --- a/plugins/main/public/components/common/modules/module.scss +++ b/plugins/main/public/components/common/modules/module.scss @@ -7,14 +7,9 @@ background: #fafbfd; } -.wz-module-header-agent { - height: 50px; - padding: 16px; -} - -.wz-module-header-agent .euiHealth svg { - width: 16px; - height: 24px; +.wz-module-header-agent .euiTab__content { + font-size: 16px !important; + font-weight: 400; } .wzApp .euiFlyoutBody .euiFlyoutBody__overflowContent { @@ -25,6 +20,10 @@ margin-top: -16px; } +.wz-module-header-agent > .euiFlexGroup { + margin-top: -6px; +} + .wz-module-header-nav > .euiFlexGroup .euiFlexItem { margin-top: 0px; } @@ -39,20 +38,9 @@ border-radius: 100px; } -.wz-module-header-agent-title-btn { - cursor: pointer; -} - .wz-font-weight-normal { font-weight: normal; } -.wz-module-header-agent h1 { - font-weight: 400; -} - -.wz-module-header-agent h1 b { - font-weight: 500; -} .wz-module-header-nav .euiTabs { padding-top: 6px; @@ -96,11 +84,6 @@ discover-app-w .sidebar-container { @media only screen and (max-width: 767px) { .wz-module-header-agent { height: auto; - h1 { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } } .wz-module-body { margin-top: -160px; diff --git a/plugins/main/public/components/common/modules/modules-defaults.tsx b/plugins/main/public/components/common/modules/modules-defaults.tsx index 0fc4b4b5bc..215c3dd14f 100644 --- a/plugins/main/public/components/common/modules/modules-defaults.tsx +++ b/plugins/main/public/components/common/modules/modules-defaults.tsx @@ -444,7 +444,13 @@ export const ModulesDefaults = { ], availableFor: ['manager', 'agent'], }, - syscollector: { + software: { + notModule: true, + }, + network: { + notModule: true, + }, + processes: { notModule: true, }, configuration: { diff --git a/plugins/main/public/components/common/ribbon/ribbon-item.scss b/plugins/main/public/components/common/ribbon/ribbon-item.scss new file mode 100644 index 0000000000..bf543a6a40 --- /dev/null +++ b/plugins/main/public/components/common/ribbon/ribbon-item.scss @@ -0,0 +1,4 @@ +.wz-ribbon-item .euiStat .euiText { + font-size: 12px; + font-family: sans-serif; +} diff --git a/plugins/main/public/components/common/ribbon/ribbon-item.tsx b/plugins/main/public/components/common/ribbon/ribbon-item.tsx new file mode 100644 index 0000000000..29741c8a6b --- /dev/null +++ b/plugins/main/public/components/common/ribbon/ribbon-item.tsx @@ -0,0 +1,148 @@ +import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; +import { WAZUH_AGENTS_OS_TYPE } from '../../../../common/constants'; +import { AgentStatus } from '../../agents/agent-status'; +import { Agent } from '../../endpoints-summary/types'; +import { WzStat } from '../../wz-stat'; +import { GroupTruncate } from '../util/agent-group-truncate'; +import WzTextWithTooltipIfTruncated from '../wz-text-with-tooltip-if-truncated'; +import './ribbon-item.scss'; +import { getAgentOSType } from '../../../react-services'; + +const FONT_SIZE = 12; + +export enum RibbonItemLabel { + GROUPS = 'groups', + OPERATING_SYSTEM = 'operating-system', + AGENT_STATUS = 'agent-status', +} + +export type IRibbonItem