mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
217 lines
6.7 KiB
TypeScript
217 lines
6.7 KiB
TypeScript
import React, { useCallback } from "react";
|
|
import { InjectedRouter } from "react-router";
|
|
import { SingleValue } from "react-select-5";
|
|
|
|
import { IGetHostSoftwareResponse } from "services/entities/hosts";
|
|
import { IGetDeviceSoftwareResponse } from "services/entities/device_user";
|
|
|
|
import { getNextLocationPath } from "utilities/helpers";
|
|
import { convertParamsToSnakeCase, QueryParams } from "utilities/url";
|
|
import { SUPPORT_LINK } from "utilities/constants";
|
|
|
|
import { HostPlatform, isAndroid } from "interfaces/platform";
|
|
|
|
import DropdownWrapper from "components/forms/fields/DropdownWrapper";
|
|
import TableContainer from "components/TableContainer";
|
|
import { ITableQueryData } from "components/TableContainer/TableContainer";
|
|
import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper";
|
|
|
|
import EmptySoftwareTable from "pages/SoftwarePage/components/tables/EmptySoftwareTable";
|
|
import TableCount from "components/TableContainer/TableCount";
|
|
import EmptyTable from "components/EmptyTable";
|
|
import CustomLink from "components/CustomLink";
|
|
|
|
import { DROPDOWN_OPTIONS, IHostSWLibraryDropdownFilterVal } from "../helpers";
|
|
|
|
const DEFAULT_PAGE_SIZE = 20;
|
|
|
|
const baseClass = "host-sw-library-table";
|
|
|
|
interface IHostSoftwareLibraryTableProps {
|
|
tableConfig: any; // TODO: type
|
|
data?: IGetHostSoftwareResponse | IGetDeviceSoftwareResponse;
|
|
platform: HostPlatform;
|
|
isLoading: boolean;
|
|
router: InjectedRouter;
|
|
sortHeader: string;
|
|
sortDirection: "asc" | "desc";
|
|
searchQuery: string;
|
|
page: number;
|
|
pagePath: string;
|
|
selfService: boolean;
|
|
}
|
|
|
|
const HostSoftwareLibraryTable = ({
|
|
tableConfig,
|
|
data,
|
|
platform,
|
|
isLoading,
|
|
router,
|
|
sortHeader,
|
|
sortDirection,
|
|
searchQuery,
|
|
selfService,
|
|
page,
|
|
pagePath,
|
|
}: IHostSoftwareLibraryTableProps) => {
|
|
const determineQueryParamChange = useCallback(
|
|
(newTableQuery: ITableQueryData) => {
|
|
const changedEntry = Object.entries(newTableQuery).find(([key, val]) => {
|
|
switch (key) {
|
|
case "searchQuery":
|
|
return val !== searchQuery;
|
|
case "sortDirection":
|
|
return val !== sortDirection;
|
|
case "sortHeader":
|
|
return val !== sortHeader;
|
|
case "pageIndex":
|
|
return val !== page;
|
|
default:
|
|
return false;
|
|
}
|
|
});
|
|
return changedEntry?.[0] ?? "";
|
|
},
|
|
[page, searchQuery, sortDirection, sortHeader]
|
|
);
|
|
|
|
const generateNewQueryParams = useCallback(
|
|
(newTableQuery: ITableQueryData, changedParam: string) => {
|
|
const newQueryParam: QueryParams = {
|
|
query: newTableQuery.searchQuery,
|
|
order_direction: newTableQuery.sortDirection,
|
|
order_key: newTableQuery.sortHeader,
|
|
page: changedParam === "pageIndex" ? newTableQuery.pageIndex : 0,
|
|
};
|
|
|
|
return newQueryParam;
|
|
},
|
|
[]
|
|
);
|
|
|
|
// TODO: Look into useDebounceCallback with dependencies
|
|
const onQueryChange = useCallback(
|
|
async (newTableQuery: ITableQueryData) => {
|
|
// 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. Can also happen when
|
|
// the filter dropdown is changed. That is handled on the onChange handler
|
|
// for the dropdown.
|
|
if (changedParam === "") return;
|
|
|
|
const newRoute = getNextLocationPath({
|
|
pathPrefix: pagePath,
|
|
routeTemplate: "",
|
|
queryParams: generateNewQueryParams(newTableQuery, changedParam),
|
|
});
|
|
|
|
router.replace(newRoute);
|
|
},
|
|
[determineQueryParamChange, pagePath, generateNewQueryParams, router]
|
|
);
|
|
|
|
const handleCustomFilterDropdownChange = (
|
|
value: IHostSWLibraryDropdownFilterVal
|
|
) => {
|
|
const queryParams: QueryParams = {
|
|
query: searchQuery,
|
|
orderDirection: sortDirection,
|
|
orderKey: sortHeader,
|
|
page: 0, // resets page index
|
|
selfService: value === "selfService",
|
|
};
|
|
|
|
router.replace(
|
|
getNextLocationPath({
|
|
pathPrefix: pagePath,
|
|
routeTemplate: "",
|
|
queryParams: convertParamsToSnakeCase(queryParams),
|
|
})
|
|
);
|
|
};
|
|
|
|
const count = data?.count || data?.software?.length || 0;
|
|
const isSoftwareNotDetected = count === 0 && searchQuery === "";
|
|
|
|
const memoizedSoftwareCount = useCallback(() => {
|
|
if (isSoftwareNotDetected) {
|
|
return null;
|
|
}
|
|
|
|
return <TableCount name="items" count={count} />;
|
|
}, [count, isSoftwareNotDetected]);
|
|
|
|
const memoizedEmptyComponent = useCallback(() => {
|
|
return <EmptySoftwareTable noSearchQuery={searchQuery === ""} />;
|
|
}, [searchQuery]);
|
|
|
|
// Determines if a user should be able to filter or search in the table
|
|
const hasData = data && data.software.length > 0;
|
|
const hasQuery = searchQuery !== "";
|
|
|
|
const showFilterHeaders = hasData || hasQuery;
|
|
|
|
if (isAndroid(platform)) {
|
|
return (
|
|
<EmptyTable
|
|
header="Installers are not supported for this host"
|
|
info={
|
|
<>
|
|
Interested in installing software on Android hosts?{" "}
|
|
<CustomLink url={SUPPORT_LINK} text="Let us know" newTab />
|
|
</>
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const renderCustomControls = () => {
|
|
return (
|
|
<div className={`${baseClass}__filter-controls`}>
|
|
<DropdownWrapper
|
|
name="host-library-filter"
|
|
value={selfService ? "selfService" : "available"}
|
|
className={`${baseClass}__host-library-filter`}
|
|
options={DROPDOWN_OPTIONS}
|
|
onChange={(newValue: SingleValue<CustomOptionType>) =>
|
|
newValue &&
|
|
handleCustomFilterDropdownChange(
|
|
newValue.value as IHostSWLibraryDropdownFilterVal
|
|
)
|
|
}
|
|
variant="table-filter"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className={baseClass}>
|
|
<TableContainer
|
|
renderCount={memoizedSoftwareCount}
|
|
columnConfigs={tableConfig}
|
|
data={data?.software || []}
|
|
isLoading={isLoading}
|
|
defaultSortHeader={sortHeader}
|
|
defaultSortDirection={sortDirection}
|
|
defaultSearchQuery={searchQuery}
|
|
pageIndex={page}
|
|
disableNextPage={data?.meta.has_next_results === false}
|
|
pageSize={DEFAULT_PAGE_SIZE}
|
|
inputPlaceHolder="Search by name"
|
|
onQueryChange={onQueryChange}
|
|
customControl={showFilterHeaders ? renderCustomControls : undefined}
|
|
emptyComponent={memoizedEmptyComponent}
|
|
showMarkAllPages={false}
|
|
isAllPagesSelected={false}
|
|
searchable={showFilterHeaders}
|
|
manualSortBy
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default HostSoftwareLibraryTable;
|