mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #40702 New look: <img width="812" height="350" alt="image" src="https://github.com/user-attachments/assets/83e82480-b756-4c51-be3f-09a72e736770" /> # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] 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. - [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 * **Bug Fixes** * Simplified pending status labels in OS Settings modal by removing "(pending)" suffix from states like "Enforcing" and "Removing enforcement" * Improved OS Settings modal table layout and styling * **New Features** * Added dedicated action buttons to resend MDM profiles and rotate Recovery Lock password * Enhanced error tooltip handling for failed profile states <!-- end of auto-generated comment: release notes by coderabbit.ai -->
160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import React, { useContext, useState } from "react";
|
|
import classnames from "classnames";
|
|
import { noop } from "lodash";
|
|
|
|
import { REC_LOCK_SYNTHETIC_PROFILE_UUID } from "pages/hosts/details/helpers";
|
|
|
|
import { NotificationContext } from "context/notification";
|
|
import { FLEET_ANDROID_CERTIFICATE_TEMPLATE_PROFILE_ID } from "interfaces/mdm";
|
|
import { getErrorReason } from "interfaces/errors";
|
|
|
|
import Button from "components/buttons/Button";
|
|
import Icon from "components/Icon";
|
|
|
|
import { IHostMdmProfileWithAddedStatus } from "../OSSettingsTableConfig";
|
|
|
|
const baseClass = "os-settings-resend-cell";
|
|
|
|
interface IResendButtonProps {
|
|
isResending: boolean;
|
|
onClick: (evt: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
|
}
|
|
|
|
const ResendButton = ({ isResending, onClick }: IResendButtonProps) => {
|
|
const classNames = classnames(`${baseClass}__resend-button`, "resend-link", {
|
|
[`${baseClass}__resending`]: isResending,
|
|
});
|
|
|
|
const buttonText = isResending ? "Resending..." : "Resend";
|
|
|
|
return (
|
|
<Button
|
|
disabled={isResending}
|
|
onClick={onClick}
|
|
variant="inverse"
|
|
className={classNames}
|
|
size="small"
|
|
>
|
|
<Icon name="refresh" color="ui-fleet-black-75" size="small" />
|
|
{buttonText}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
interface IRotateButtonProps {
|
|
isRotating: boolean;
|
|
onClick: () => void;
|
|
}
|
|
|
|
const RotateButton = ({ isRotating, onClick }: IRotateButtonProps) => {
|
|
const classNames = classnames(`${baseClass}__rotate-button`, "rotate-link", {
|
|
[`${baseClass}__rotating`]: isRotating,
|
|
});
|
|
|
|
const buttonText = isRotating ? "Rotating..." : "Rotate";
|
|
|
|
return (
|
|
<Button
|
|
disabled={isRotating}
|
|
onClick={onClick}
|
|
variant="inverse"
|
|
className={classNames}
|
|
size="small"
|
|
>
|
|
<Icon name="refresh" color="ui-fleet-black-75" size="small" />
|
|
{buttonText}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
interface IOSSettingsResendCellProps {
|
|
canResendProfiles: boolean;
|
|
canRotateRecoveryLockPassword?: boolean;
|
|
profile: IHostMdmProfileWithAddedStatus;
|
|
resendRequest: (profileUUID: string) => Promise<void>;
|
|
resendCertificateRequest?: (certificateTemplateId: number) => Promise<void>;
|
|
rotateRecoveryLockPassword?: () => Promise<void>;
|
|
onProfileResent?: () => void;
|
|
}
|
|
|
|
const OSSettingsResendCell = ({
|
|
canResendProfiles,
|
|
canRotateRecoveryLockPassword = false,
|
|
profile,
|
|
resendRequest,
|
|
resendCertificateRequest,
|
|
rotateRecoveryLockPassword,
|
|
onProfileResent = noop,
|
|
}: IOSSettingsResendCellProps) => {
|
|
const { renderFlash } = useContext(NotificationContext);
|
|
const [isResending, setIsResending] = useState(false);
|
|
const [isRotating, setIsRotating] = useState(false);
|
|
|
|
const isAndroidCertificate =
|
|
profile.profile_uuid === FLEET_ANDROID_CERTIFICATE_TEMPLATE_PROFILE_ID;
|
|
|
|
const onResendProfile = async () => {
|
|
setIsResending(true);
|
|
try {
|
|
if (
|
|
isAndroidCertificate &&
|
|
resendCertificateRequest &&
|
|
profile.certificate_template_id !== undefined
|
|
) {
|
|
await resendCertificateRequest(profile.certificate_template_id);
|
|
renderFlash(
|
|
"success",
|
|
"Successfully sent request to resend certificate."
|
|
);
|
|
onProfileResent();
|
|
} else if (!isAndroidCertificate) {
|
|
await resendRequest(profile.profile_uuid);
|
|
onProfileResent();
|
|
}
|
|
} catch (e) {
|
|
renderFlash("error", "Couldn't resend. Please try again.");
|
|
}
|
|
setIsResending(false);
|
|
};
|
|
|
|
const onRotatePassword = async () => {
|
|
if (!rotateRecoveryLockPassword) return;
|
|
setIsRotating(true);
|
|
try {
|
|
await rotateRecoveryLockPassword();
|
|
renderFlash(
|
|
"success",
|
|
"Successfully sent request to rotate Recovery Lock password."
|
|
);
|
|
} catch (e) {
|
|
const msg = getErrorReason(e).includes("already in progress")
|
|
? "Recovery lock password rotation is already in progress for this host."
|
|
: "Couldn't send request to rotate Recovery Lock password. Please try again.";
|
|
|
|
renderFlash("error", msg);
|
|
}
|
|
setIsRotating(false);
|
|
};
|
|
|
|
const isFailed = profile.status === "failed";
|
|
const isVerified = profile.status === "verified";
|
|
const showResendButton =
|
|
canResendProfiles &&
|
|
(isFailed || isVerified) &&
|
|
profile.profile_uuid !== REC_LOCK_SYNTHETIC_PROFILE_UUID;
|
|
const showRotateButton =
|
|
canRotateRecoveryLockPassword && (isFailed || isVerified);
|
|
|
|
return (
|
|
<div className={baseClass}>
|
|
{showResendButton && (
|
|
<ResendButton isResending={isResending} onClick={onResendProfile} />
|
|
)}
|
|
{showRotateButton && (
|
|
<RotateButton isRotating={isRotating} onClick={onRotatePassword} />
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default OSSettingsResendCell;
|