mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Allowed clearing Windows OS update deadline (#42272)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #33418 (this OS update change is unrelated to the bigger Windows delete part of the story) <img width="598" height="438" alt="image" src="https://github.com/user-attachments/assets/7dca50c6-5ca4-4c54-b57f-c98dda5fb4d1" /> # 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`. ## Testing - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Windows update deadline and grace period settings can now be cleared, allowing removal of update enforcement policies. * **Bug Fixes** * Updated validation logic to properly handle empty deadline and grace period fields. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
34e0ace5f6
commit
c6538bd434
4 changed files with 95 additions and 70 deletions
1
changes/allow-clearing-windows-update-settings
Normal file
1
changes/allow-clearing-windows-update-settings
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Allowed clearing Windows OS update deadline and grace period fields to remove enforcement.
|
||||
|
|
@ -1061,23 +1061,34 @@ const TAGGED_TEMPLATES = {
|
|||
);
|
||||
},
|
||||
editedWindowsUpdates: (activity: IActivity) => {
|
||||
const deadlineDays = activity.details?.deadline_days;
|
||||
const gracePeriodDays = activity.details?.grace_period_days;
|
||||
const isCleared = deadlineDays === undefined || deadlineDays === null;
|
||||
const teamText = activity.details?.team_name ? (
|
||||
<>
|
||||
the <b>{activity.details.team_name}</b> fleet
|
||||
</>
|
||||
) : (
|
||||
`unassigned`
|
||||
);
|
||||
|
||||
if (isCleared) {
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
removed the Windows OS update options on hosts assigned to {teamText}.
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
updated the Windows OS update options (
|
||||
<b>
|
||||
Deadline: {activity.details?.deadline_days} days / Grace period:{" "}
|
||||
{activity.details?.grace_period_days} days
|
||||
Deadline: {deadlineDays} days / Grace period: {gracePeriodDays} days
|
||||
</b>
|
||||
) on hosts assigned to{" "}
|
||||
{activity.details?.team_name ? (
|
||||
<>
|
||||
the <b>{activity.details.team_name}</b> fleet
|
||||
</>
|
||||
) : (
|
||||
`unassigned`
|
||||
)}
|
||||
.
|
||||
) on hosts assigned to {teamText}.
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import teamsAPI from "services/entities/teams";
|
|||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import Button from "components/buttons/Button";
|
||||
import validatePresence from "components/forms/validators/validate_presence";
|
||||
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
|
||||
|
||||
const baseClass = "windows-target-form";
|
||||
|
|
@ -45,14 +44,22 @@ const validateGracePeriodDays = (value: string) => {
|
|||
|
||||
const validateForm = (formData: IWindowsTargetFormData) => {
|
||||
const errors: IWindowsTargetFormErrors = {};
|
||||
const deadlineEmpty = formData.deadlineDays.trim() === "";
|
||||
const graceEmpty = formData.gracePeriodDays.trim() === "";
|
||||
|
||||
if (!validatePresence(formData.deadlineDays)) {
|
||||
errors.deadlineDays = "The deadline days is required.";
|
||||
} else if (!validateDeadlineDays(formData.deadlineDays)) {
|
||||
// Both empty is valid (clears enforcement)
|
||||
if (deadlineEmpty && graceEmpty) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (!deadlineEmpty && !validateDeadlineDays(formData.deadlineDays)) {
|
||||
errors.deadlineDays = "Deadline must meet criteria below.";
|
||||
}
|
||||
|
||||
if (!validatePresence(formData.gracePeriodDays)) {
|
||||
if (deadlineEmpty && !graceEmpty) {
|
||||
errors.gracePeriodDays =
|
||||
"Grace period must be empty if no deadline is set.";
|
||||
} else if (!deadlineEmpty && graceEmpty) {
|
||||
errors.gracePeriodDays = "The grace period days is required.";
|
||||
} else if (!validateGracePeriodDays(formData.gracePeriodDays)) {
|
||||
errors.gracePeriodDays = "Grace period must meet criteria below.";
|
||||
|
|
@ -64,8 +71,8 @@ const validateForm = (formData: IWindowsTargetFormData) => {
|
|||
interface IWindowsMdmConfigData {
|
||||
mdm: {
|
||||
windows_updates: {
|
||||
deadline_days: number;
|
||||
grace_period_days: number;
|
||||
deadline_days: number | null;
|
||||
grace_period_days: number | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -77,8 +84,10 @@ const createMdmConfigData = (
|
|||
return {
|
||||
mdm: {
|
||||
windows_updates: {
|
||||
deadline_days: parseInt(deadlineDays, 10),
|
||||
grace_period_days: parseInt(gracePeriodDays, 10),
|
||||
deadline_days:
|
||||
deadlineDays.trim() === "" ? null : parseInt(deadlineDays, 10),
|
||||
grace_period_days:
|
||||
gracePeriodDays.trim() === "" ? null : parseInt(gracePeriodDays, 10),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -104,59 +113,61 @@ const WindowsTargetForm = ({
|
|||
.gitops_mode_enabled;
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [deadlineDays, setDeadlineDays] = useState(
|
||||
defaultDeadlineDays.toString()
|
||||
);
|
||||
const [gracePeriodDays, setGracePeriodDays] = useState(
|
||||
defaultGracePeriodDays.toString()
|
||||
);
|
||||
const [deadlineDaysError, setDeadlineDaysError] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [gracePeriodDaysError, setGracePeriodDaysError] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [formData, setFormData] = useState<IWindowsTargetFormData>({
|
||||
deadlineDays: defaultDeadlineDays.toString(),
|
||||
gracePeriodDays: defaultGracePeriodDays.toString(),
|
||||
});
|
||||
const [formErrors, setFormErrors] = useState<IWindowsTargetFormErrors>({});
|
||||
|
||||
// FIXME: This behaves unexpectedly when a user switches tabs or changes the teams dropdown while the form is
|
||||
// submitting because this component is unmounted.
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const errors = validateForm({
|
||||
deadlineDays,
|
||||
gracePeriodDays,
|
||||
});
|
||||
const errors = validateForm(formData);
|
||||
if (!isEmpty(errors)) {
|
||||
setFormErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
setDeadlineDaysError(errors.deadlineDays);
|
||||
setGracePeriodDaysError(errors.gracePeriodDays);
|
||||
|
||||
if (isEmpty(errors)) {
|
||||
setIsSaving(true);
|
||||
const updateData = createMdmConfigData(deadlineDays, gracePeriodDays);
|
||||
try {
|
||||
currentTeamId === APP_CONTEXT_NO_TEAM_ID
|
||||
? await configAPI.update(updateData)
|
||||
: await teamsAPI.update(updateData, currentTeamId);
|
||||
renderFlash(
|
||||
"success",
|
||||
"Successfully updated Windows OS update options."
|
||||
);
|
||||
} catch {
|
||||
renderFlash("error", "Couldn’t update. Please try again.");
|
||||
} finally {
|
||||
currentTeamId === APP_CONTEXT_NO_TEAM_ID
|
||||
? refetchAppConfig()
|
||||
: refetchTeamConfig();
|
||||
setIsSaving(false);
|
||||
}
|
||||
setIsSaving(true);
|
||||
const updateData = createMdmConfigData(
|
||||
formData.deadlineDays,
|
||||
formData.gracePeriodDays
|
||||
);
|
||||
try {
|
||||
currentTeamId === APP_CONTEXT_NO_TEAM_ID
|
||||
? await configAPI.update(updateData)
|
||||
: await teamsAPI.update(updateData, currentTeamId);
|
||||
renderFlash("success", "Successfully updated Windows OS update options.");
|
||||
} catch {
|
||||
renderFlash("error", "Couldn’t update. Please try again.");
|
||||
} finally {
|
||||
currentTeamId === APP_CONTEXT_NO_TEAM_ID
|
||||
? refetchAppConfig()
|
||||
: refetchTeamConfig();
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeadlineDaysChange = (val: string) => {
|
||||
setDeadlineDays(val);
|
||||
const handleChange = (field: keyof IWindowsTargetFormData) => (
|
||||
val: string
|
||||
) => {
|
||||
const newFormData = { ...formData, [field]: val };
|
||||
setFormData(newFormData);
|
||||
// On change, only update/clear existing errors (optimistic UX)
|
||||
const newErrors = validateForm(newFormData);
|
||||
const updatedErrors: IWindowsTargetFormErrors = {};
|
||||
Object.keys(formErrors).forEach((key) => {
|
||||
const k = key as keyof IWindowsTargetFormErrors;
|
||||
if (newErrors[k]) {
|
||||
updatedErrors[k] = newErrors[k];
|
||||
}
|
||||
});
|
||||
setFormErrors(updatedErrors);
|
||||
};
|
||||
|
||||
const handleGracePeriodDays = (val: string) => {
|
||||
setGracePeriodDays(val);
|
||||
const handleBlur = () => {
|
||||
setFormErrors(validateForm(formData));
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -166,18 +177,20 @@ const WindowsTargetForm = ({
|
|||
label="Deadline"
|
||||
tooltip="Number of days the end user has before updates are installed and the host is forced to restart."
|
||||
helpText="Number of days from 0 to 30."
|
||||
value={deadlineDays}
|
||||
error={deadlineDaysError}
|
||||
onChange={handleDeadlineDaysChange}
|
||||
value={formData.deadlineDays}
|
||||
error={formErrors.deadlineDays}
|
||||
onChange={handleChange("deadlineDays")}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
<InputField
|
||||
disabled={gitOpsModeEnabled}
|
||||
label="Grace period"
|
||||
tooltip="Number of days after the deadline the end user has before the host is forced to restart (only if end user was offline when deadline passed)."
|
||||
helpText="Number of days from 0 to 7."
|
||||
value={gracePeriodDays}
|
||||
error={gracePeriodDaysError}
|
||||
onChange={handleGracePeriodDays}
|
||||
value={formData.gracePeriodDays}
|
||||
error={formErrors.gracePeriodDays}
|
||||
onChange={handleChange("gracePeriodDays")}
|
||||
onBlur={handleBlur}
|
||||
/>{" "}
|
||||
<div className="button-wrap">
|
||||
<GitOpsModeTooltipWrapper
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ export interface IUpdateTeamFormData {
|
|||
deadline: string;
|
||||
};
|
||||
windows_updates?: {
|
||||
deadline_days: number;
|
||||
grace_period_days: number;
|
||||
deadline_days: number | null;
|
||||
grace_period_days: number | null;
|
||||
};
|
||||
};
|
||||
host_expiry_settings: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue