/** software/vulnerabilities Vulnerabilities tab > Table */ import React, { useCallback, useContext, useMemo } from "react"; import { InjectedRouter } from "react-router"; import { Row } from "react-table"; import PATHS from "router/paths"; import { AppContext } from "context/app"; import { GITHUB_NEW_ISSUE_LINK, VULNERABILITIES_SEARCH_BOX_TOOLTIP, } from "utilities/constants"; import { isIncompleteQuoteQuery } from "utilities/strings/stringUtils"; import CustomLink from "components/CustomLink"; import TableContainer from "components/TableContainer"; import LastUpdatedText from "components/LastUpdatedText"; import { ITableQueryData } from "components/TableContainer/TableContainer"; import TableCount from "components/TableContainer/TableCount"; import { SingleValue } from "react-select-5"; import DropdownWrapper from "components/forms/fields/DropdownWrapper"; import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper"; import EmptyVulnerabilitiesTable from "pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/EmptyVulnerabilitiesTable"; import { IVulnerabilitiesResponse, IVulnerabilitiesEmptyStateReason, } from "services/entities/vulnerabilities"; import { getPathWithQueryParams } from "utilities/url"; import { getNextLocationPath } from "utilities/helpers"; import generateTableConfig from "./VulnerabilitiesTableConfig"; import { getExploitedVulnerabilitiesDropdownOptions } from "./helpers"; const baseClass = "software-vulnerabilities-table"; interface IRowProps extends Row { original: { cve?: string; }; } interface ISoftwareVulnerabilitiesTableProps { router: InjectedRouter; isSoftwareEnabled: boolean; data?: IVulnerabilitiesResponse; emptyStateReason?: IVulnerabilitiesEmptyStateReason; query?: string; perPage: number; orderDirection: "asc" | "desc"; orderKey: string; showExploitedVulnerabilitiesOnly: boolean; currentPage: number; teamId?: number; isLoading: boolean; } const SoftwareVulnerabilitiesTable = ({ router, isSoftwareEnabled, data, emptyStateReason, query, perPage, orderDirection, orderKey, showExploitedVulnerabilitiesOnly, currentPage, teamId, isLoading, }: ISoftwareVulnerabilitiesTableProps) => { const { isPremiumTier } = useContext(AppContext); const determineQueryParamChange = useCallback( (newTableQuery: ITableQueryData) => { const changedEntry = Object.entries(newTableQuery).find(([key, val]) => { switch (key) { case "sortDirection": return val !== orderDirection; case "sortHeader": return val !== orderKey; case "pageIndex": return val !== currentPage; case "searchQuery": return val !== query; case "exploit": return val !== showExploitedVulnerabilitiesOnly.toString(); default: return false; } }); return changedEntry?.[0] ?? ""; }, [ currentPage, orderDirection, orderKey, query, showExploitedVulnerabilitiesOnly, ] ); const generateNewQueryParams = useCallback( (newTableQuery: ITableQueryData, changedParam: string) => { return { fleet_id: teamId, exploit: showExploitedVulnerabilitiesOnly.toString(), query: newTableQuery.searchQuery, order_direction: newTableQuery.sortDirection, order_key: newTableQuery.sortHeader, page: changedParam === "pageIndex" ? newTableQuery.pageIndex : 0, }; }, [teamId, showExploitedVulnerabilitiesOnly] ); const onQueryChange = useCallback( (newTableQuery: ITableQueryData) => { // We don't want to start searching until a user completes their quote query if (isIncompleteQuoteQuery(newTableQuery.searchQuery)) { return; } // we want to determine which query param has changed in order to // reset the page index to 0 if any other param has changed. const changedParam = determineQueryParamChange(newTableQuery); // if nothing has changed, don't update the route. this can happen when // this handler is called on the inital render. if (changedParam === "") return; const newRoute = getNextLocationPath({ pathPrefix: PATHS.SOFTWARE_VULNERABILITIES, routeTemplate: "", queryParams: generateNewQueryParams(newTableQuery, changedParam), }); router.replace(newRoute); }, [determineQueryParamChange, generateNewQueryParams, router] ); // determines if a user be able to search in the table const searchable = isSoftwareEnabled && (!!data?.vulnerabilities || query !== "" || showExploitedVulnerabilitiesOnly); const vulnerabilitiesTableHeaders = useMemo(() => { if (!data) return []; return generateTableConfig( isPremiumTier, router, { includeName: true, includeVulnerabilities: true, includeIcon: true, }, teamId ); }, [data, isPremiumTier, router, teamId]); const handleExploitedVulnFilterDropdownChange = ( isFilterExploited: string ) => { router.replace( getNextLocationPath({ pathPrefix: PATHS.SOFTWARE_VULNERABILITIES, routeTemplate: "", queryParams: { query, fleet_id: teamId, order_direction: orderDirection, order_key: orderKey, exploit: isFilterExploited, page: 0, // resets page index }, }) ); }; const handleRowSelect = (row: IRowProps) => { if (row.original.cve) { const cveName = row.original.cve.toString(); const softwareVulnerabilityDetailsPath = getPathWithQueryParams( PATHS.SOFTWARE_VULNERABILITY_DETAILS(cveName), { fleet_id: teamId, } ); router.push(softwareVulnerabilityDetailsPath); } }; const renderVulnerabilityCount = () => { if (!data) return null; const count = data?.count; return ( <> {data?.vulnerabilities && data?.counts_updated_at && ( The last time software data was
updated, including vulnerabilities
and host counts. } /> )} ); }; const renderTableHelpText = () => { return (
Seeing unexpected software or vulnerabilities?{" "}
); }; // Exploited vulnerabilities is a premium feature const renderExploitedVulnerabilitiesDropdown = () => { return ( ) => newValue && handleExploitedVulnFilterDropdownChange(newValue.value) } variant="table-filter" /> ); }; return (
( )} defaultSearchQuery={query} defaultSortHeader={orderKey} defaultSortDirection={orderDirection} pageIndex={currentPage} manualSortBy pageSize={perPage} showMarkAllPages={false} isAllPagesSelected={false} disableNextPage={!data?.meta.has_next_results} searchable={searchable} searchQueryColumn="vulnerability" inputPlaceHolder="Search by CVE" searchToolTipText={VULNERABILITIES_SEARCH_BOX_TOOLTIP} onQueryChange={onQueryChange} customControl={ searchable ? renderExploitedVulnerabilitiesDropdown : undefined } stackControls renderCount={renderVulnerabilityCount} renderTableHelpText={renderTableHelpText} disableMultiRowSelect onSelectSingleRow={handleRowSelect} />
); }; export default SoftwareVulnerabilitiesTable;