fleet/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsResendCell/OSSettingsResendCell.tsx
Magnus Jensen 90f75f1644
simplify OS modal (#43252)
<!-- 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 -->
2026-04-09 16:30:15 -05:00

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;