Fleet UI: Disable install/uninstall actions if scripts are disabled (#22240)

This commit is contained in:
RachelElysia 2024-09-20 13:19:43 -07:00 committed by GitHub
parent fc8b1d67f5
commit d7594d1f1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 156 additions and 40 deletions

View file

@ -282,6 +282,36 @@ const removeUnavailableOptions = (
return options;
};
// Available tooltips for disabled options
export const getDropdownOptionTooltipContent = (
value: string | number,
isHostOnline?: boolean
) => {
const tooltipAction: Record<string, string> = {
runScript: "run scripts on",
wipe: "wipe",
lock: "lock",
unlock: "unlock",
installSoftware: "install software on", // Host software dropdown option
uninstallSoftware: "uninstall software on", // Host software dropdown option
};
if (tooltipAction[value]) {
return (
<>
To {tooltipAction[value]} this host, deploy the
<br />
fleetd agent with --enable-scripts and
<br />
refetch host vitals
</>
);
}
if (!isHostOnline && value === "query") {
return <>You can&apos;t query an offline host.</>;
}
return undefined;
};
const modifyOptions = (
options: IDropdownOption[],
{
@ -291,34 +321,13 @@ const modifyOptions = (
hostPlatform,
}: IHostActionConfigOptions
) => {
// Available tooltips for disabled options
const getDropdownOptionTooltipContent = (value: string | number) => {
const tooltipAction: Record<string, string> = {
runScript: "run scripts on",
wipe: "wipe",
lock: "lock",
unlock: "unlock",
};
if (tooltipAction[value]) {
return (
<>
To {tooltipAction[value]} this host, deploy the
<br />
fleetd agent with --enable-scripts and
<br />
refetch host vitals
</>
);
}
if (!isHostOnline && value === "query") {
return <>You can&apos;t query an offline host.</>;
}
};
const disableOptions = (optionsToDisable: IDropdownOption[]) => {
optionsToDisable.forEach((option) => {
option.disabled = true;
option.tooltipContent = getDropdownOptionTooltipContent(option.value);
option.tooltipContent = getDropdownOptionTooltipContent(
option.value,
isHostOnline
);
});
};

View file

@ -946,6 +946,7 @@ const HostDetailsPage = ({
platform={host.platform}
softwareUpdatedAt={host.software_updated_at}
hostCanWriteSoftware={!!host.orbit_version || isIosOrIpadosHost}
hostScriptsEnabled={host.scripts_enabled || false}
isSoftwareEnabled={featuresConfig?.enable_software_inventory}
router={router}
queryParams={parseHostSoftwareQueryParams(location.query)}

View file

@ -44,6 +44,7 @@ interface IHostSoftwareProps {
hostTeamId: number;
onShowSoftwareDetails?: (software: IHostSoftware) => void;
isSoftwareEnabled?: boolean;
hostScriptsEnabled?: boolean;
isMyDevicePage?: boolean;
}
@ -87,6 +88,7 @@ const HostSoftware = ({
platform,
softwareUpdatedAt,
hostCanWriteSoftware,
hostScriptsEnabled,
router,
queryParams,
pathname,
@ -249,6 +251,7 @@ const HostSoftware = ({
router,
softwareIdActionPending,
userHasSWWritePermission,
hostScriptsEnabled,
onSelectAction,
teamId: hostTeamId,
hostCanWriteSoftware,
@ -258,6 +261,7 @@ const HostSoftware = ({
router,
softwareIdActionPending,
userHasSWWritePermission,
hostScriptsEnabled,
onSelectAction,
hostTeamId,
hostCanWriteSoftware,

View file

@ -0,0 +1,83 @@
import {
generateActions,
DEFAULT_ACTION_OPTIONS,
generateActionsProps,
} from "./HostSoftwareTableConfig";
describe("generateActions", () => {
const defaultProps: generateActionsProps = {
userHasSWWritePermission: true,
hostScriptsEnabled: true,
hostCanWriteSoftware: true,
softwareIdActionPending: null,
softwareId: 1,
status: null,
software_package: null,
app_store_app: null,
};
it("returns default actions when user has write permission and scripts are enabled", () => {
const actions = generateActions(defaultProps);
expect(actions).toEqual(DEFAULT_ACTION_OPTIONS);
});
it("removes install and uninstall actions when user has no write permission", () => {
const props = { ...defaultProps, userHasSWWritePermission: false };
const actions = generateActions(props);
expect(actions.find((a) => a.value === "install")).toBeUndefined();
expect(actions.find((a) => a.value === "uninstall")).toBeUndefined();
});
it("disables install and uninstall actions when host scripts are disabled", () => {
const props = { ...defaultProps, hostScriptsEnabled: false };
const actions = generateActions(props);
expect(actions.find((a) => a.value === "install")?.disabled).toBe(true);
expect(actions.find((a) => a.value === "uninstall")?.disabled).toBe(true);
});
it("disables install and uninstall actions when locally pending (waiting for API response)", () => {
const props = {
...defaultProps,
softwareIdActionPending: 1,
softwareId: 1,
};
const actions = generateActions(props);
expect(actions.find((a) => a.value === "install")?.disabled).toBe(true);
expect(actions.find((a) => a.value === "uninstall")?.disabled).toBe(true);
});
it("disables install and uninstall actions when pending install status", () => {
const props: generateActionsProps = {
...defaultProps,
status: "pending_install",
};
const actions = generateActions(props);
expect(actions.find((a) => a.value === "install")?.disabled).toBe(true);
expect(actions.find((a) => a.value === "uninstall")?.disabled).toBe(true);
});
it("disables install and uninstall actions when pending uninstall status", () => {
const props: generateActionsProps = {
...defaultProps,
status: "pending_uninstall",
};
const actions = generateActions(props);
expect(actions.find((a) => a.value === "install")?.disabled).toBe(true);
expect(actions.find((a) => a.value === "uninstall")?.disabled).toBe(true);
});
it("removes uninstall action for VPP apps", () => {
const props: generateActionsProps = {
...defaultProps,
app_store_app: {
app_store_id: "1",
self_service: false,
icon_url: "",
version: "",
last_install: { command_uuid: "", installed_at: "" },
},
};
const actions = generateActions(props);
expect(actions.find((a) => a.value === "uninstall")).toBeUndefined();
});
});

View file

@ -29,8 +29,9 @@ import VersionCell from "pages/SoftwarePage/components/VersionCell";
import { getVulnerabilities } from "pages/SoftwarePage/SoftwareTitles/SoftwareTable/SoftwareTitlesTableConfig";
import InstallStatusCell from "./InstallStatusCell";
import { getDropdownOptionTooltipContent } from "../../HostDetailsPage/HostActionsDropdown/helpers";
const DEFAULT_ACTION_OPTIONS: IDropdownOption[] = [
export const DEFAULT_ACTION_OPTIONS: IDropdownOption[] = [
{ value: "showDetails", label: "Show details", disabled: false },
{ value: "install", label: "Install", disabled: false },
{ value: "uninstall", label: "Uninstall", disabled: false },
@ -50,24 +51,25 @@ type IInstalledVersionsCellProps = CellProps<
>;
type IVulnerabilitiesCellProps = IInstalledVersionsCellProps;
const generateActions = ({
userHasSWWritePermission,
// Commenting below in case there is a quick decision to use these conditions after all
// hostCanWriteSoftware,
// software_package,
softwareIdActionPending,
softwareId,
status,
app_store_app,
}: {
export interface generateActionsProps {
userHasSWWritePermission: boolean;
hostScriptsEnabled: boolean;
hostCanWriteSoftware: boolean;
softwareIdActionPending: number | null;
softwareId: number;
status: SoftwareInstallStatus | null;
software_package: IHostSoftwarePackage | null;
app_store_app: IHostAppStoreApp | null;
}) => {
}
export const generateActions = ({
userHasSWWritePermission,
hostScriptsEnabled,
softwareIdActionPending,
softwareId,
status,
app_store_app,
}: generateActionsProps) => {
// this gives us a clean slate of the default actions so we can modify
// the options.
const actions = cloneDeep(DEFAULT_ACTION_OPTIONS);
@ -88,15 +90,29 @@ const generateActions = ({
}
if (!userHasSWWritePermission) {
actions.splice(indexInstallAction, 1);
// Reverse order to not change index of subsequent array element before removal
actions.splice(indexUninstallAction, 1);
actions.splice(indexInstallAction, 1);
} else {
// if host's scripts are disabled, disable install/uninstall with tooltip
if (!hostScriptsEnabled) {
actions[indexInstallAction].disabled = true;
actions[indexUninstallAction].disabled = true;
actions[
indexInstallAction
].tooltipContent = getDropdownOptionTooltipContent("installSoftware");
actions[
indexUninstallAction
].tooltipContent = getDropdownOptionTooltipContent("uninstallSoftware");
}
// user has software write permission for host
const pendingStatuses = ["pending_install", "pending_uninstall"];
// if locally pending (waiting for API response) or pending install/uninstall,
// disable both install and uninstall
if (
// if locally pending (waiting for API response) or pending install/uninstall, disable both
// install and uninstall
softwareId === softwareIdActionPending ||
pendingStatuses.includes(status || "")
) {
@ -114,6 +130,7 @@ const generateActions = ({
interface ISoftwareTableHeadersProps {
userHasSWWritePermission: boolean;
hostScriptsEnabled?: boolean;
hostCanWriteSoftware: boolean;
softwareIdActionPending: number | null;
router: InjectedRouter;
@ -125,6 +142,7 @@ interface ISoftwareTableHeadersProps {
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
export const generateSoftwareTableHeaders = ({
userHasSWWritePermission,
hostScriptsEnabled = false,
hostCanWriteSoftware,
softwareIdActionPending,
router,
@ -217,6 +235,7 @@ export const generateSoftwareTableHeaders = ({
placeholder="Actions"
options={generateActions({
userHasSWWritePermission,
hostScriptsEnabled,
hostCanWriteSoftware,
softwareIdActionPending,
softwareId,