Skip to content

Commit 32c42c3

Browse files
jacobshandlingJacob Shandling
andauthored
UI - Show software details My device page (#25022)
## #23315 - On device user page > Software, make rows clickable and on click, open the Software details modal to display information about the installation on the host. - Update Software details modal copy and allow long file paths to wrap https://github.com/user-attachments/assets/1e714c5e-1614-46c0-bb56-d6dc8ad4f8ae <img width="1350" alt="Screenshot 2024-12-26 at 10 27 44 AM" src="https://github.com/user-attachments/assets/5cefc45a-b0ef-41d9-84e6-21ac17aaeffe" /> <img width="1350" alt="Screenshot 2024-12-26 at 10 27 19 AM" src="https://github.com/user-attachments/assets/e0866961-31a4-4bd3-82e8-18f72cf4dc30" /> <img width="1350" alt="Screenshot 2024-12-26 at 10 27 37 AM" src="https://github.com/user-attachments/assets/2bf6c880-664d-4315-8a40-8de61a5e4748" /> - [x] Changes file added for user-visible changes in `changes/`, - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
1 parent e94aa2d commit 32c42c3

File tree

13 files changed

+84
-21
lines changed

13 files changed

+84
-21
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* Add the ability to click a software row on the my device page and see the details of that
2+
software's installation on the host.

frontend/components/LiveQuery/TargetsInput/TargetsInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const TargetsInput = ({
122122
disablePagination
123123
disableMultiRowSelect
124124
onClickRow={handleRowSelect}
125-
keyboardSelectableRow
125+
keyboardSelectableRows
126126
/>
127127
</div>
128128
)}

frontend/components/TableContainer/DataTable/DataTable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ interface IDataTableProps {
4646
sortDirection: any;
4747
onSort: any; // TODO: an event type
4848
disableMultiRowSelect: boolean;
49-
keyboardSelectableRow?: boolean;
49+
keyboardSelectableRows?: boolean;
5050
showMarkAllPages: boolean;
5151
isAllPagesSelected: boolean; // TODO: make dependent on showMarkAllPages
5252
toggleAllPagesSelected?: any; // TODO: an event type and make it dependent on showMarkAllPages
@@ -95,7 +95,7 @@ const DataTable = ({
9595
sortDirection,
9696
onSort,
9797
disableMultiRowSelect,
98-
keyboardSelectableRow,
98+
keyboardSelectableRows,
9999
showMarkAllPages,
100100
isAllPagesSelected,
101101
toggleAllPagesSelected,
@@ -575,7 +575,7 @@ const DataTable = ({
575575
},
576576
})}
577577
// Can tab onto an entire row if a child element does not have the same onClick functionality as clicking the whole row
578-
tabIndex={keyboardSelectableRow ? 0 : -1}
578+
tabIndex={keyboardSelectableRows ? 0 : -1}
579579
>
580580
{row.cells.map((cell: any) => {
581581
return (

frontend/components/TableContainer/TableContainer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ interface ITableContainerProps<T = any> {
9494
*/
9595
onClickRow?: (row: T) => void;
9696
/** Used if users can click the row and another child element does not have the same onClick functionality */
97-
keyboardSelectableRow?: boolean;
97+
keyboardSelectableRows?: boolean;
9898
/** Use for clientside filtering: Use key global for filtering on any column, or use column id as
9999
* key */
100100
filters?: Record<string, string | number | boolean>;
@@ -162,7 +162,7 @@ const TableContainer = <T,>({
162162
stackControls,
163163
onSelectSingleRow,
164164
onClickRow,
165-
keyboardSelectableRow,
165+
keyboardSelectableRows,
166166
renderCount,
167167
renderTableHelpText,
168168
setExportRows,
@@ -514,7 +514,7 @@ const TableContainer = <T,>({
514514
secondarySelectActions={secondarySelectActions}
515515
onSelectSingleRow={onSelectSingleRow}
516516
onClickRow={onClickRow}
517-
keyboardSelectableRow={keyboardSelectableRow}
517+
keyboardSelectableRows={keyboardSelectableRows}
518518
onResultsCountChange={setClientFilterCount}
519519
isClientSidePagination={isClientSidePagination}
520520
onClientSidePaginationChange={onClientSidePaginationChange}

frontend/components/TableContainer/_styles.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@
257257
transition: 250ms;
258258
text-overflow: none;
259259
}
260-
&:hover {
260+
&:hover,
261+
&:focus-visible {
261262
.row-hover-link {
262263
opacity: 1;
263264
}

frontend/components/ViewAllHostsLink/ViewAllHostsLink.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ interface IHostLinkProps {
1313
platformLabelId?: number;
1414
/** Shows right chevron without text */
1515
condensed?: boolean;
16+
excludeChevron?: boolean;
1617
responsive?: boolean;
1718
customText?: string;
18-
/** Table links shows on row hover only */
19+
/** Table links shows on row hover and tab focus only */
1920
rowHover?: boolean;
2021
// don't actually create a link, useful when click is handled by an ancestor
2122
noLink?: boolean;
@@ -28,6 +29,7 @@ const ViewAllHostsLink = ({
2829
className,
2930
platformLabelId,
3031
condensed = false,
32+
excludeChevron = false,
3133
responsive = false,
3234
customText,
3335
rowHover = false,
@@ -58,11 +60,13 @@ const ViewAllHostsLink = ({
5860
{customText ?? "View all hosts"}
5961
</span>
6062
)}
61-
<Icon
62-
name="chevron-right"
63-
className={`${baseClass}__icon`}
64-
color="core-fleet-blue"
65-
/>
63+
{!excludeChevron && (
64+
<Icon
65+
name="chevron-right"
66+
className={`${baseClass}__icon`}
67+
color="core-fleet-blue"
68+
/>
69+
)}
6670
</Link>
6771
);
6872
};

frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from "interfaces/host";
1717
import { IHostPolicy } from "interfaces/policy";
1818
import { IDeviceGlobalConfig } from "interfaces/config";
19+
import { IHostSoftware } from "interfaces/software";
1920

2021
import DeviceUserError from "components/DeviceUserError";
2122
// @ts-ignore
@@ -52,6 +53,7 @@ import OSSettingsModal from "../OSSettingsModal";
5253
import BootstrapPackageModal from "../HostDetailsPage/modals/BootstrapPackageModal";
5354
import { parseHostSoftwareQueryParams } from "../cards/Software/HostSoftware";
5455
import SelfService from "../cards/Software/SelfService";
56+
import SoftwareDetailsModal from "../cards/Software/SoftwareDetailsModal";
5557
import DeviceUserBanners from "./components/DeviceUserBanners";
5658

5759
const baseClass = "device-user";
@@ -118,6 +120,10 @@ const DeviceUserPage = ({
118120
const [isTriggeringCreateLinuxKey, setIsTriggeringCreateLinuxKey] = useState(
119121
false
120122
);
123+
const [
124+
selectedSoftwareDetails,
125+
setSelectedSoftwareDetails,
126+
] = useState<IHostSoftware | null>(null);
121127

122128
const { data: deviceMapping, refetch: refetchDeviceMapping } = useQuery(
123129
["deviceMapping", deviceAuthToken],
@@ -455,6 +461,7 @@ const DeviceUserPage = ({
455461
platform={host.platform}
456462
hostTeamId={host.team_id || 0}
457463
isSoftwareEnabled={isSoftwareEnabled}
464+
onShowSoftwareDetails={setSelectedSoftwareDetails}
458465
/>
459466
</TabPanel>
460467
)}
@@ -508,6 +515,13 @@ const DeviceUserPage = ({
508515
}}
509516
/>
510517
)}
518+
{selectedSoftwareDetails && !!host && (
519+
<SoftwareDetailsModal
520+
hostDisplayName={host.display_name}
521+
software={selectedSoftwareDetails}
522+
onExit={() => setSelectedSoftwareDetails(null)}
523+
/>
524+
)}
511525
</div>
512526
);
513527
};

frontend/pages/hosts/details/cards/Software/DeviceSoftwareTableConfig.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import VulnerabilitiesCell from "pages/SoftwarePage/components/VulnerabilitiesCe
1515
import VersionCell from "pages/SoftwarePage/components/VersionCell";
1616
import { getVulnerabilities } from "pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTitlesTableConfig";
1717
import SoftwareNameCell from "components/TableContainer/DataTable/SoftwareNameCell";
18+
import ViewAllHostsLink from "components/ViewAllHostsLink";
1819

1920
type ISoftwareTableConfig = Column<IHostSoftware>;
2021
type ITableHeaderProps = IHeaderProps<IHostSoftware>;
@@ -84,6 +85,21 @@ export const generateSoftwareTableHeaders = (): ISoftwareTableConfig[] => {
8485
return <VulnerabilitiesCell vulnerabilities={vulnerabilities} />;
8586
},
8687
},
88+
{
89+
Header: "",
90+
// accessor ends up defining the classname for this column (`id__header` in this case), but is
91+
// type restricted, so using "id" here, which is unsued by another column
92+
accessor: "id",
93+
disableSortBy: true,
94+
Cell: () => (
95+
<ViewAllHostsLink
96+
rowHover
97+
noLink
98+
excludeChevron
99+
customText="Show details"
100+
/>
101+
),
102+
},
87103
];
88104

89105
return tableHeaders;

frontend/pages/hosts/details/cards/Software/HostSoftware.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ interface IHostSoftwareProps {
4242
queryParams: ReturnType<typeof parseHostSoftwareQueryParams>;
4343
pathname: string;
4444
hostTeamId: number;
45-
onShowSoftwareDetails?: (software: IHostSoftware) => void;
45+
onShowSoftwareDetails: (software: IHostSoftware) => void;
4646
isSoftwareEnabled?: boolean;
4747
hostScriptsEnabled?: boolean;
4848
isMyDevicePage?: boolean;
@@ -329,6 +329,9 @@ const HostSoftware = ({
329329
pagePath={pathname}
330330
hostSoftwareFilter={getHostSoftwareFilterFromQueryParams()}
331331
pathPrefix={pathname}
332+
// for my device software details modal toggling
333+
isMyDevicePage={isMyDevicePage}
334+
onShowSoftwareDetails={onShowSoftwareDetails}
332335
/>
333336
)}
334337
</>

frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import Dropdown from "components/forms/fields/Dropdown";
2323
import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable";
2424
import TableCount from "components/TableContainer/TableCount";
2525
import { VulnsNotSupported } from "pages/SoftwarePage/components/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable";
26+
import { Row } from "react-table";
27+
import { IHostSoftware } from "interfaces/software";
2628

2729
const DEFAULT_PAGE_SIZE = 20;
2830

@@ -50,6 +52,9 @@ export const DROPDOWN_OPTIONS = [
5052
},
5153
] as const;
5254

55+
interface IHostSoftwareRowProps extends Row {
56+
original: IHostSoftware;
57+
}
5358
interface IHostSoftwareTableProps {
5459
tableConfig: any; // TODO: type
5560
data?: IGetHostSoftwareResponse | IGetDeviceSoftwareResponse;
@@ -64,6 +69,8 @@ interface IHostSoftwareTableProps {
6469
routeTemplate?: string;
6570
pathPrefix: string;
6671
hostSoftwareFilter: IHostSoftwareDropdownFilterVal;
72+
isMyDevicePage?: boolean;
73+
onShowSoftwareDetails: (software: IHostSoftware) => void;
6774
}
6875

6976
const HostSoftwareTable = ({
@@ -80,6 +87,8 @@ const HostSoftwareTable = ({
8087
routeTemplate,
8188
pathPrefix,
8289
hostSoftwareFilter,
90+
isMyDevicePage,
91+
onShowSoftwareDetails,
8392
}: IHostSoftwareTableProps) => {
8493
const handleFilterDropdownChange = useCallback(
8594
(val: IHostSoftwareDropdownFilterVal) => {
@@ -221,6 +230,13 @@ const HostSoftwareTable = ({
221230

222231
const showFilterHeaders = hasData || hasQuery || hasSoftwareFilter;
223232

233+
const onClickMyDeviceRow = useCallback(
234+
(row: IHostSoftwareRowProps) => {
235+
onShowSoftwareDetails(row.original);
236+
},
237+
[onShowSoftwareDetails]
238+
);
239+
224240
return (
225241
<div className={baseClass}>
226242
<TableContainer
@@ -242,6 +258,10 @@ const HostSoftwareTable = ({
242258
isAllPagesSelected={false}
243259
searchable={showFilterHeaders}
244260
manualSortBy
261+
keyboardSelectableRows
262+
// my device page row clickability
263+
disableMultiRowSelect={isMyDevicePage}
264+
onClickRow={isMyDevicePage ? onClickMyDeviceRow : undefined}
245265
/>
246266
</div>
247267
);

frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/SoftwareDetailsModal.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { dateAgo } from "utilities/date_format";
1818

1919
import { AppInstallDetails } from "components/ActivityDetails/InstallDetails/AppInstallDetails";
2020
import { SoftwareInstallDetails } from "components/ActivityDetails/InstallDetails/SoftwareInstallDetails";
21-
import TooltipTruncatedText from "components/TooltipTruncatedText";
2221

2322
const baseClass = "software-details-modal";
2423

@@ -68,11 +67,11 @@ const SoftwareDetailsInfo = ({
6867
<div className={`${baseClass}__row`}>
6968
<DataSet
7069
className={`${baseClass}__file-path-data-set`}
71-
title="File path"
70+
title={`File path${installed_paths.length > 1 ? "s" : ""}`}
7271
value={
7372
<div className={`${baseClass}__file-path-values`}>
7473
{installed_paths.map((path) => (
75-
<TooltipTruncatedText value={path} />
74+
<span>{path}</span>
7675
))}
7776
</div>
7877
}

frontend/pages/hosts/details/cards/Software/SoftwareDetailsModal/_styles.scss

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@
4141

4242
> span {
4343
width: 100%;
44-
overflow: hidden;
45-
text-overflow: ellipsis;
46-
white-space: nowrap;
44+
white-space: initial;
45+
overflow-wrap: anywhere;
4746
}
4847
}
4948

frontend/pages/hosts/details/cards/Software/_styles.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
.Vulnerabilities__header {
3939
display: table-cell;
4040
}
41+
// this is called `id` only due to typing concerns in the table config – it is for the
42+
// "Show details" column
43+
.id__header {
44+
width: px-to-rem(90);
45+
}
4146
@media (max-width: $break-xl) {
4247
.source__header {
4348
display: none;

0 commit comments

Comments
 (0)