mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Clear passcode frontend (#43084)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #42369 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. **Done in backend task for whole story** - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added "Clear passcode" action for iOS and iPad hosts in the host actions menu, accessible only to Premium tier users with appropriate permissions. * Added confirmation modal for clearing device passcodes. * Passcode clearing activity now appears in the activity feed with actor information. * Action is conditionally disabled during specific device states (Lost Mode, pending wipe) with contextual tooltips. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
36ad83f611
commit
bc32339526
12 changed files with 458 additions and 3 deletions
|
|
@ -164,6 +164,7 @@ export enum ActivityType {
|
|||
EditedEnrollSecrets = "edited_enroll_secrets",
|
||||
AddedMicrosoftEntraTenant = "added_microsoft_entra_tenant",
|
||||
DeletedMicrosoftEntraTenant = "deleted_microsoft_entra_tenant",
|
||||
ClearedPasscode = "cleared_passcode",
|
||||
}
|
||||
|
||||
/** This is a subset of ActivityType that are shown only for the host past activities */
|
||||
|
|
@ -184,7 +185,8 @@ export type IHostPastActivityType =
|
|||
| ActivityType.CanceledInstallSoftware
|
||||
| ActivityType.CanceledUninstallSoftware
|
||||
| ActivityType.InstalledCertificate
|
||||
| ActivityType.ResentCertificate;
|
||||
| ActivityType.ResentCertificate
|
||||
| ActivityType.ClearedPasscode;
|
||||
|
||||
/** This is a subset of ActivityType that are shown only for the host upcoming activities */
|
||||
export type IHostUpcomingActivityType =
|
||||
|
|
@ -465,4 +467,5 @@ export const ACTIVITY_TYPE_TO_FILTER_LABEL: Record<ActivityType, string> = {
|
|||
[ActivityType.DeletedCertificate]: "Deleted certificate",
|
||||
[ActivityType.InstalledCertificate]: "Installed certificate",
|
||||
[ActivityType.EditedEnrollSecrets]: "Edited enroll secrets",
|
||||
[ActivityType.ClearedPasscode]: "Cleared passcode",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1799,6 +1799,13 @@ const TAGGED_TEMPLATES = {
|
|||
<> deleted Microsoft Entra tenant ({activity.details?.tenant_id}).</>
|
||||
);
|
||||
},
|
||||
clearedPasscode: (activity: IActivity) => {
|
||||
return (
|
||||
<>
|
||||
cleared the passcode on <b>{activity.details?.host_display_name}</b>.
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const getDetail = (activity: IActivity, isPremiumTier: boolean) => {
|
||||
|
|
@ -2204,6 +2211,9 @@ const getDetail = (activity: IActivity, isPremiumTier: boolean) => {
|
|||
case ActivityType.DeletedMicrosoftEntraTenant: {
|
||||
return TAGGED_TEMPLATES.deletedMicrosoftEntraTenant(activity);
|
||||
}
|
||||
case ActivityType.ClearedPasscode: {
|
||||
return TAGGED_TEMPLATES.clearedPasscode(activity);
|
||||
}
|
||||
default: {
|
||||
return TAGGED_TEMPLATES.defaultActivityTemplate(activity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import createMockUser from "__mocks__/userMock";
|
|||
import createMockTeam from "__mocks__/teamMock";
|
||||
|
||||
import HostActionsDropdown from "./HostActionsDropdown";
|
||||
import { HostMdmDeviceStatusUIState } from "../../helpers";
|
||||
|
||||
describe("Host Actions Dropdown", () => {
|
||||
describe("Transfer action", () => {
|
||||
|
|
@ -1404,7 +1405,7 @@ describe("Host Actions Dropdown", () => {
|
|||
});
|
||||
|
||||
describe("Render options only available for iOS and iPadOS", () => {
|
||||
it("renders only the transfer, wipe, and delete options for iOS", async () => {
|
||||
it("renders only the transfer, wipe, clear passcode, and delete options for iOS", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
|
|
@ -1433,6 +1434,7 @@ describe("Host Actions Dropdown", () => {
|
|||
|
||||
expect(screen.queryByText("Transfer")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Wipe")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Clear passcode")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Delete")).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText("Live report")).not.toBeInTheDocument();
|
||||
|
|
@ -1442,7 +1444,7 @@ describe("Host Actions Dropdown", () => {
|
|||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders only the transfer, wipe, and delete options for iPadOS", async () => {
|
||||
it("renders only the transfer, wipe, clear passcode, and delete options for iPadOS", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
|
|
@ -1471,6 +1473,7 @@ describe("Host Actions Dropdown", () => {
|
|||
|
||||
expect(screen.queryByText("Transfer")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Wipe")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Clear passcode")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Delete")).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText("Live report")).not.toBeInTheDocument();
|
||||
|
|
@ -1512,6 +1515,7 @@ describe("Host Actions Dropdown", () => {
|
|||
expect(screen.getByText("Transfer")).toBeInTheDocument();
|
||||
expect(screen.getByText("Delete")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Live report")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Clear passcode")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Run script")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Wipe")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Lock")).not.toBeInTheDocument();
|
||||
|
|
@ -1551,6 +1555,7 @@ describe("Host Actions Dropdown", () => {
|
|||
expect(screen.getByText("Transfer")).toBeInTheDocument();
|
||||
expect(screen.getByText("Delete")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Live report")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Clear passcode")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Run script")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Wipe")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Lock")).not.toBeInTheDocument();
|
||||
|
|
@ -1735,4 +1740,252 @@ describe("Host Actions Dropdown", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Clear passcode action", () => {
|
||||
it("renders the action when an iOS host is enrolled in MDM", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
isGlobalAdmin: true,
|
||||
isPremiumTier: true,
|
||||
isMacMdmEnabledAndConfigured: true,
|
||||
currentUser: createMockUser(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
<HostActionsDropdown
|
||||
hostTeamId={null}
|
||||
onSelect={noop}
|
||||
hostStatus="online"
|
||||
hostPlatform="ios"
|
||||
hostMdmEnrollmentStatus="On (company-owned)"
|
||||
isConnectedToFleetMdm
|
||||
hostMdmDeviceStatus="unlocked"
|
||||
hostScriptsEnabled
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
expect(screen.queryByText("Clear passcode")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render for below maintainer", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
isGlobalTechnician: true,
|
||||
isGlobalAdmin: false,
|
||||
isTeamMaintainer: false,
|
||||
isTeamAdmin: false,
|
||||
isPremiumTier: true,
|
||||
isMacMdmEnabledAndConfigured: true,
|
||||
currentUser: createMockUser(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<HostActionsDropdown
|
||||
hostTeamId={1}
|
||||
onSelect={noop}
|
||||
hostStatus="online"
|
||||
hostPlatform="ios"
|
||||
hostMdmEnrollmentStatus="On (company-owned)"
|
||||
isConnectedToFleetMdm
|
||||
hostMdmDeviceStatus="unlocked"
|
||||
hostScriptsEnabled
|
||||
/>
|
||||
);
|
||||
|
||||
// Component returns null when no options are available for this role,
|
||||
// so neither the Actions button nor Clear passcode are rendered.
|
||||
expect(screen.queryByText("Actions")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Clear passcode")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render for non-iOS hosts", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
isGlobalAdmin: true,
|
||||
isPremiumTier: true,
|
||||
isMacMdmEnabledAndConfigured: true,
|
||||
currentUser: createMockUser(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
<HostActionsDropdown
|
||||
hostTeamId={null}
|
||||
onSelect={noop}
|
||||
hostStatus="online"
|
||||
hostPlatform="darwin"
|
||||
hostMdmEnrollmentStatus="On (company-owned)"
|
||||
isConnectedToFleetMdm
|
||||
hostMdmDeviceStatus="unlocked"
|
||||
hostScriptsEnabled
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
expect(screen.queryByText("Clear passcode")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("is hidden if Apple MDM is not enabled", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
isGlobalAdmin: true,
|
||||
isPremiumTier: true,
|
||||
isMacMdmEnabledAndConfigured: false,
|
||||
currentUser: createMockUser(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
<HostActionsDropdown
|
||||
hostTeamId={null}
|
||||
onSelect={noop}
|
||||
hostStatus="online"
|
||||
hostPlatform="ios"
|
||||
hostMdmEnrollmentStatus="On (company-owned)"
|
||||
isConnectedToFleetMdm
|
||||
hostMdmDeviceStatus="unlocked"
|
||||
hostScriptsEnabled
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
expect(screen.queryByText("Clear passcode")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("is hidden on Fleet free", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
isGlobalAdmin: true,
|
||||
isPremiumTier: false,
|
||||
isMacMdmEnabledAndConfigured: true,
|
||||
currentUser: createMockUser(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
<HostActionsDropdown
|
||||
hostTeamId={null}
|
||||
onSelect={noop}
|
||||
hostStatus="online"
|
||||
hostPlatform="ios"
|
||||
hostMdmEnrollmentStatus="On (company-owned)"
|
||||
isConnectedToFleetMdm
|
||||
hostMdmDeviceStatus="unlocked"
|
||||
hostScriptsEnabled
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
expect(screen.queryByText("Clear passcode")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each<HostMdmDeviceStatusUIState>([
|
||||
"locked",
|
||||
"locking",
|
||||
"unlocking",
|
||||
"locating",
|
||||
])("is disabled with tooltip when host status is %s", async (status) => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
isGlobalAdmin: true,
|
||||
isPremiumTier: true,
|
||||
isMacMdmEnabledAndConfigured: true,
|
||||
currentUser: createMockUser(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
<HostActionsDropdown
|
||||
hostTeamId={null}
|
||||
onSelect={noop}
|
||||
hostStatus="online"
|
||||
hostPlatform="ios"
|
||||
hostMdmEnrollmentStatus="On (company-owned)"
|
||||
isConnectedToFleetMdm
|
||||
hostMdmDeviceStatus={status}
|
||||
hostScriptsEnabled
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
const option = screen.getByText("Clear passcode");
|
||||
expect(option).toBeInTheDocument();
|
||||
expect(option).toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
await user.hover(option);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Clear passcode is unavailable while host is in Lost Mode./i
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it.each<HostMdmDeviceStatusUIState>(["wiping", "wiped"])(
|
||||
"is disabled with tooltip when pending wipe",
|
||||
async (status) => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
app: {
|
||||
isGlobalAdmin: true,
|
||||
isPremiumTier: true,
|
||||
isMacMdmEnabledAndConfigured: true,
|
||||
currentUser: createMockUser(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { user } = render(
|
||||
<HostActionsDropdown
|
||||
hostTeamId={null}
|
||||
onSelect={noop}
|
||||
hostStatus="online"
|
||||
hostPlatform="ios"
|
||||
hostMdmEnrollmentStatus="On (company-owned)"
|
||||
isConnectedToFleetMdm
|
||||
hostMdmDeviceStatus={status}
|
||||
hostScriptsEnabled
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
const option = screen.getByText("Clear passcode");
|
||||
expect(option).toBeInTheDocument();
|
||||
expect(option).toHaveAttribute("aria-disabled", "true");
|
||||
|
||||
await user.hover(option);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Clear passcode is unavailable while host is pending wipe./i
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -69,6 +69,11 @@ const DEFAULT_OPTIONS = [
|
|||
value: "unlock",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "Clear passcode",
|
||||
value: "clearPasscode",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
disabled: false,
|
||||
|
|
@ -312,6 +317,43 @@ const canShowRecoveryLockPassword = (config: IHostActionConfigOptions) => {
|
|||
return isRecoveryLockPasswordEnabled;
|
||||
};
|
||||
|
||||
const canClearPasscode = (config: IHostActionConfigOptions) => {
|
||||
if (!config.isPremiumTier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isIPadOrIPhone(config.hostPlatform)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.isEnrolledInMdm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.isConnectedToFleetMdm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.isMacMdmEnabledAndConfigured) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
config.hostMdmEnrollmentStatus !== "On (company-owned)" &&
|
||||
config.hostMdmEnrollmentStatus !== "On (automatic)" &&
|
||||
config.hostMdmEnrollmentStatus !== "On (manual)"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
config.isGlobalAdmin ||
|
||||
config.isGlobalMaintainer ||
|
||||
config.isTeamAdmin ||
|
||||
config.isTeamMaintainer
|
||||
);
|
||||
};
|
||||
|
||||
const canRunScript = ({
|
||||
hostPlatform,
|
||||
isGlobalAdmin,
|
||||
|
|
@ -356,6 +398,10 @@ const removeUnavailableOptions = (
|
|||
);
|
||||
}
|
||||
|
||||
if (!canClearPasscode(config)) {
|
||||
options = options.filter((option) => option.value !== "clearPasscode");
|
||||
}
|
||||
|
||||
if (!canTurnOffMdm(config)) {
|
||||
options = options.filter((option) => option.value !== "mdmOff");
|
||||
}
|
||||
|
|
@ -552,6 +598,24 @@ const modifyOptions = (
|
|||
}
|
||||
}
|
||||
|
||||
const clearPasscodeOption = options.find(
|
||||
(option) => option.value === "clearPasscode"
|
||||
);
|
||||
if (
|
||||
clearPasscodeOption &&
|
||||
["locked", "locking", "unlocking", "locating"].includes(hostMdmDeviceStatus)
|
||||
) {
|
||||
clearPasscodeOption.disabled = true;
|
||||
clearPasscodeOption.tooltipContent =
|
||||
"Clear passcode is unavailable while host is in Lost Mode.";
|
||||
} else if (
|
||||
clearPasscodeOption &&
|
||||
["wiped", "wiping"].includes(hostMdmDeviceStatus)
|
||||
) {
|
||||
clearPasscodeOption.disabled = true;
|
||||
clearPasscodeOption.tooltipContent =
|
||||
"Clear passcode is unavailable while host is pending wipe.";
|
||||
}
|
||||
disableOptions(optionsToDisable);
|
||||
formatTurnOffOptionLabel(options, hostPlatform);
|
||||
return options;
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ import InventoryVersionsModal from "../modals/InventoryVersionsModal";
|
|||
import UpdateEndUserModal from "../cards/User/components/UpdateEndUserModal";
|
||||
import LocationModal from "../modals/LocationModal";
|
||||
import MDMStatusModal from "../modals/MDMStatusModal";
|
||||
import ClearPasscodeModal from "./modals/ClearPasscodeModal";
|
||||
|
||||
const baseClass = "host-details";
|
||||
|
||||
|
|
@ -237,6 +238,7 @@ const HostDetailsPage = ({
|
|||
boolean | undefined
|
||||
>(false);
|
||||
const [showMDMStatusModal, setShowMDMStatusModal] = useState(false);
|
||||
const [showClearPasscodeModal, setShowClearPasscodeModal] = useState(false);
|
||||
|
||||
// General-use updating state
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
|
@ -682,6 +684,10 @@ const HostDetailsPage = ({
|
|||
setShowMDMStatusModal(!showMDMStatusModal);
|
||||
}, [showMDMStatusModal, setShowMDMStatusModal]);
|
||||
|
||||
const toggleClearPasscodeModal = useCallback(() => {
|
||||
setShowClearPasscodeModal(!showClearPasscodeModal);
|
||||
}, [showClearPasscodeModal, setShowClearPasscodeModal]);
|
||||
|
||||
const onCancelPolicyDetailsModal = useCallback(() => {
|
||||
setPolicyDetailsModal(!showPolicyDetailsModal);
|
||||
setSelectedPolicy(null);
|
||||
|
|
@ -960,6 +966,9 @@ const HostDetailsPage = ({
|
|||
case "wipe":
|
||||
setShowWipeModal(true);
|
||||
break;
|
||||
case "clearPasscode":
|
||||
setShowClearPasscodeModal(true);
|
||||
break;
|
||||
default: // do nothing
|
||||
}
|
||||
};
|
||||
|
|
@ -1761,6 +1770,9 @@ const HostDetailsPage = ({
|
|||
onExit={toggleMDMStatusModal}
|
||||
/>
|
||||
)}
|
||||
{showClearPasscodeModal && (
|
||||
<ClearPasscodeModal id={host.id} onExit={toggleClearPasscodeModal} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import React, { useContext } from "react";
|
||||
|
||||
import { NotificationContext } from "context/notification";
|
||||
import hostAPI from "services/entities/hosts";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import Button from "components/buttons/Button";
|
||||
|
||||
const baseClass = "clear-passcode-modal";
|
||||
|
||||
interface IClearPasscodeModalProps {
|
||||
id: number;
|
||||
onExit: () => void;
|
||||
}
|
||||
|
||||
const ClearPasscodeModal = ({ id, onExit }: IClearPasscodeModalProps) => {
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
const [isClearingPasscode, setIsClearingPasscode] = React.useState(false);
|
||||
|
||||
const onClearPasscode = async () => {
|
||||
setIsClearingPasscode(true);
|
||||
try {
|
||||
await hostAPI.clearPasscode(id);
|
||||
renderFlash(
|
||||
"success",
|
||||
"Successfully sent request to clear passcode on this host."
|
||||
);
|
||||
} catch (e) {
|
||||
renderFlash(
|
||||
"error",
|
||||
"Couldn't send request to clear passcode on this host. Please try again."
|
||||
);
|
||||
} finally {
|
||||
onExit();
|
||||
setIsClearingPasscode(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderModalContent = () => {
|
||||
return (
|
||||
<p>
|
||||
This will remove the current passcode and allow anyone with physical
|
||||
access to unlock the host.
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const renderModalButtons = () => {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onClearPasscode}
|
||||
className="clear-passcode-loading"
|
||||
variant="alert"
|
||||
isLoading={isClearingPasscode}
|
||||
>
|
||||
Clear Passcode
|
||||
</Button>
|
||||
<Button onClick={onExit} variant="inverse-alert">
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal className={baseClass} title="Clear passcode" onExit={onExit}>
|
||||
<div className={`${baseClass}__modal-content`}>
|
||||
{renderModalContent()}
|
||||
</div>
|
||||
|
||||
<div className="modal-cta-wrap">{renderModalButtons()}</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClearPasscodeModal;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./ClearPasscodeModal";
|
||||
|
|
@ -24,6 +24,7 @@ import CanceledInstallSoftwareActivityItem from "./ActivityItems/CanceledInstall
|
|||
import CanceledUninstallSoftwareActivtyItem from "./ActivityItems/CanceledUninstallSoftwareActivtyItem";
|
||||
import InstalledCertificateActivityItem from "./ActivityItems/InstalledCertificateActivityItem";
|
||||
import ResentCertificateActivityItem from "./ActivityItems/ResentCertificateActivityItem";
|
||||
import ClearedPasscodeActivityItem from "./ActivityItems/ClearedPasscodeActivityItem";
|
||||
|
||||
/** The component props that all host activity items must adhere to */
|
||||
export interface IHostActivityItemComponentProps {
|
||||
|
|
@ -68,6 +69,7 @@ export const pastActivityComponentMap: Record<
|
|||
[ActivityType.CanceledUninstallSoftware]: CanceledUninstallSoftwareActivtyItem,
|
||||
[ActivityType.InstalledCertificate]: InstalledCertificateActivityItem,
|
||||
[ActivityType.ResentCertificate]: ResentCertificateActivityItem,
|
||||
[ActivityType.ClearedPasscode]: ClearedPasscodeActivityItem,
|
||||
};
|
||||
|
||||
export const upcomingActivityComponentMap: Record<
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import React from "react";
|
||||
|
||||
import ActivityItem from "components/ActivityItem";
|
||||
|
||||
import { IHostActivityItemComponentProps } from "../../ActivityConfig";
|
||||
|
||||
const baseClass = "cleared-passcode-activity-item";
|
||||
|
||||
const ClearedPasscodeActivityItem = ({
|
||||
activity,
|
||||
}: IHostActivityItemComponentProps) => {
|
||||
return (
|
||||
<ActivityItem
|
||||
className={baseClass}
|
||||
activity={activity}
|
||||
hideCancel
|
||||
hideShowDetails
|
||||
>
|
||||
<b>{activity.actor_full_name}</b> cleared the passcode on this host.
|
||||
</ActivityItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClearedPasscodeActivityItem;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./ClearedPasscodeActivityItem";
|
||||
|
|
@ -686,6 +686,11 @@ export default {
|
|||
return sendRequest("POST", HOST_WIPE(id));
|
||||
},
|
||||
|
||||
clearPasscode: (id: number) => {
|
||||
const { HOST_CLEAR_PASSCODE } = endpoints;
|
||||
return sendRequest("POST", HOST_CLEAR_PASSCODE(id));
|
||||
},
|
||||
|
||||
resendProfile: (hostId: number, profileUUID: string): Promise<void> => {
|
||||
const { HOST_RESEND_PROFILE } = endpoints;
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ export default {
|
|||
HOST_LOCK: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/lock`,
|
||||
HOST_UNLOCK: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/unlock`,
|
||||
HOST_WIPE: (id: number) => `/${API_VERSION}/fleet/hosts/${id}/wipe`,
|
||||
HOST_CLEAR_PASSCODE: (id: number) =>
|
||||
`/${API_VERSION}/fleet/hosts/${id}/clear_passcode`,
|
||||
HOST_RESEND_PROFILE: (hostId: number, profileUUID: string) =>
|
||||
`/${API_VERSION}/fleet/hosts/${hostId}/configuration_profiles/${profileUUID}/resend`,
|
||||
HOST_RESEND_CERTIFICATE: (hostId: number, certificateTemplateId: number) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue