mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
Fleet UI: Disable install/uninstall actions if scripts are disabled (#22240)
This commit is contained in:
parent
fc8b1d67f5
commit
d7594d1f1d
5 changed files with 156 additions and 40 deletions
|
|
@ -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'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'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
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue