diff --git a/changes/10196-surface-software-filepath b/changes/10196-surface-software-filepath new file mode 100644 index 0000000000..24917c7649 --- /dev/null +++ b/changes/10196-surface-software-filepath @@ -0,0 +1 @@ +- Users can now see the filepath of software on a host \ No newline at end of file diff --git a/frontend/components/DiskSpaceGraph/DiskSpaceGraph.tsx b/frontend/components/DiskSpaceGraph/DiskSpaceGraph.tsx index 19c81961e1..97eb1a8f9f 100644 --- a/frontend/components/DiskSpaceGraph/DiskSpaceGraph.tsx +++ b/frontend/components/DiskSpaceGraph/DiskSpaceGraph.tsx @@ -1,6 +1,7 @@ import React from "react"; import ReactTooltip from "react-tooltip"; +import { COLORS } from "styles/var/colors"; interface IDiskSpaceGraphProps { baseClass: string; @@ -71,7 +72,7 @@ const DiskSpaceGraph = ({ type="dark" effect="solid" id={`tooltip-${id}`} - backgroundColor="#3e4771" + backgroundColor={COLORS["tooltip-bg"]} > p.startsWith("team_id=")); - // URLs for the app represent "All teams" by the absence of the team id param const newTeamPart = newTeamId > APP_CONTEXT_ALL_TEAMS_ID ? `team_id=${newTeamId}` : ""; diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts index e158f7effc..9230dfc1b8 100644 --- a/frontend/interfaces/software.ts +++ b/frontend/interfaces/software.ts @@ -33,6 +33,7 @@ export interface ISoftware { vulnerabilities: IVulnerability[] | null; hosts_count?: number; last_opened_at?: string | null; // e.g., "2021-08-18T15:11:35Z” + installed_paths?: string[]; } export const TYPE_CONVERSION: Record = { diff --git a/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx b/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx index 7949a12ba0..f53985e1f1 100644 --- a/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx +++ b/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx @@ -353,6 +353,8 @@ const allHostTableHeaders: IDataColumn[] = [ backgroundColor="#3e4771" id={`device_mapping__${cellProps.row.original.id}`} data-html + clickable + delayHide={300} > {tooltipText} diff --git a/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx b/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx index 215cfc39c0..377ffc74d7 100644 --- a/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Software/SoftwareTableConfig.tsx @@ -12,6 +12,8 @@ import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCel import TextCell from "components/TableContainer/DataTable/TextCell"; import TooltipWrapper from "components/TooltipWrapper"; import ViewAllHostsLink from "components/ViewAllHostsLink"; +import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; +import { COLORS } from "styles/var/colors"; interface IHeaderProps { column: { @@ -117,6 +119,39 @@ const renderBundleTooltip = (name: string, bundle: string) => ( ); +interface IInstalledPathCellProps { + cell: { + value: string[]; + }; + row: { + original: ISoftware; + }; +} + +const condenseInstalledPaths = (installedPaths: string[]): string[] => { + if (!installedPaths?.length) { + return []; + } + const condensed = + installedPaths.length === 4 + ? installedPaths.slice(-4).reverse() + : installedPaths.slice(-3).reverse() || []; + return installedPaths.length > 4 + ? condensed.concat(`+${installedPaths.length - 3} more`) // TODO: confirm limit + : condensed; +}; + +const tooltipTextWithLineBreaks = (lines: string[]) => { + return lines.map((line) => { + return ( + + {line} +
+
+ ); + }); +}; + interface ISoftwareTableData extends Omit { vulnerabilities: string[]; } @@ -308,6 +343,53 @@ export const generateSoftwareTableHeaders = ({ }, sortType: "dateStrings", }, + { + title: "File path", + Header: () => { + return ( + + File path + + ); + }, + accessor: "installed_paths", + Cell: (cellProps: IInstalledPathCellProps): JSX.Element => { + const numInstalledPaths = cellProps.cell.value?.length || 0; + const installedPaths = condenseInstalledPaths( + cellProps.cell.value || [] + ); + if (installedPaths.length) { + const tooltipText = tooltipTextWithLineBreaks(installedPaths); + return ( + <> + 1 ? "text-muted tooltip" : "" + }`} + data-tip + data-for={`installed_paths__${cellProps.row.original.id}`} + data-tip-disable={installedPaths.length <= 1} + > + {numInstalledPaths === 1 + ? installedPaths[0] + : `${numInstalledPaths} paths`} + + + {tooltipText} + + + ); + } + return {DEFAULT_EMPTY_CELL_VALUE}; + }, + }, { title: "", Header: "", diff --git a/frontend/pages/hosts/details/cards/Software/_styles.scss b/frontend/pages/hosts/details/cards/Software/_styles.scss index 6eda90fc8c..4e26ef96fd 100644 --- a/frontend/pages/hosts/details/cards/Software/_styles.scss +++ b/frontend/pages/hosts/details/cards/Software/_styles.scss @@ -106,7 +106,7 @@ @media (min-width: $break-1400) { .source__cell { display: table-cell; - width: $col-md; + width: $col-sm; } .hosts_count__cell { .hosts-cell__wrapper { @@ -184,6 +184,10 @@ // Only show this column to macos users .macos .data-table-block .data-table__table { + .installed_paths__header { + border-right: none; + } + @media (min-width: $break-990) { thead .version__header { width: $col-sm; @@ -191,17 +195,27 @@ } @media (min-width: $break-1400) { thead { - .vulnerabilities__header { - width: $col-md; - } .last_opened_at__header { display: table-cell; } + .installed_paths__header { + width: $col-md; + } } tbody { .last_opened_at__cell { display: table-cell; } + + .installed_paths__cell { + .tooltip { + display: inline; // center tooltip with hovered text + } + } + + .linkToFilteredHosts__cell { + width: 120px; + } } } }