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;
+ }
}
}
}