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 #35459 # 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. ## Testing - [ ] Added/updated automated tests working on these - [X] QA'd all new/changed functionality manually ## Screenshots | Option does not appear for FMA apps | | --- | | <img width="723" height="419" alt="image" src="https://github.com/user-attachments/assets/f9f1328e-e38c-452c-b06e-337a69c13e71" /> | | Option does not appear for custom packages | | --- | | <img width="731" height="416" alt="image" src="https://github.com/user-attachments/assets/3de78f15-d7ce-45c7-875f-a250fc00a160" /> | | Option does not appear for macOS VPP apps | | --- | | <img width="725" height="454" alt="image" src="https://github.com/user-attachments/assets/07dcb074-f57d-4cc4-a746-20b80c821fb6" /> | | Option appears iOS VPP apps | | --- | | <img width="727" height="420" alt="image" src="https://github.com/user-attachments/assets/ec4ce503-0300-437c-b3f2-248928fcfe7b" /> | | Option appears iPadOS VPP apps | | --- | | <img width="727" height="422" alt="image" src="https://github.com/user-attachments/assets/0030c6cc-3d93-480c-af93-740fca4d5b57" /> | | Form with auto-updates disabled | | --- | | <img width="668" height="517" alt="image" src="https://github.com/user-attachments/assets/d59a7ba4-dc83-4a80-ba94-0befc7635f05" /> | | Start / end time validation | | --- | | <img width="668" height="679" alt="image" src="https://github.com/user-attachments/assets/939fd09a-76f6-42de-9c71-fe4982f3f84b" /> | | Maintenance window length validation | | --- | | <img width="664" height="681" alt="image" src="https://github.com/user-attachments/assets/a2eab676-5166-42a9-9043-2565014e33cb" /> | | Badge and banner appears after saving | | --- | | <img width="766" height="529" alt="image" src="https://github.com/user-attachments/assets/48d89e1d-4430-4dd7-b8e6-d5b04ebad47f" /> | --------- Co-authored-by: Gabriel Hernandez <ghernandez345@gmail.com> Co-authored-by: Nico <32375741+nulmete@users.noreply.github.com>
190 lines
5.4 KiB
TypeScript
190 lines
5.4 KiB
TypeScript
import { ISoftwareAutoUpdateConfigFormData } from "./EditAutoUpdateConfigModal";
|
|
|
|
export interface ISoftwareAutoUpdateConfigInputValidation {
|
|
isValid: boolean;
|
|
message?: string;
|
|
}
|
|
|
|
export interface ISoftwareAutoUpdateConfigFormValidation {
|
|
isValid: boolean;
|
|
autoUpdateStartTime?: ISoftwareAutoUpdateConfigInputValidation;
|
|
autoUpdateEndTime?: ISoftwareAutoUpdateConfigInputValidation;
|
|
targets?: ISoftwareAutoUpdateConfigInputValidation;
|
|
windowLength?: ISoftwareAutoUpdateConfigInputValidation;
|
|
}
|
|
|
|
type IMessageFunc = (formData: ISoftwareAutoUpdateConfigFormData) => string;
|
|
type IValidationMessage = string | IMessageFunc;
|
|
type IFormValidationKey = keyof Omit<
|
|
ISoftwareAutoUpdateConfigFormValidation,
|
|
"isValid"
|
|
>;
|
|
|
|
interface IValidation {
|
|
name: string;
|
|
isValid: (
|
|
formData: ISoftwareAutoUpdateConfigFormData,
|
|
validations?: ISoftwareAutoUpdateConfigFormValidation
|
|
) => boolean;
|
|
message?: IValidationMessage;
|
|
}
|
|
|
|
type IFormValidations = Record<
|
|
IFormValidationKey,
|
|
{ validations: IValidation[] }
|
|
>;
|
|
|
|
const validateTimeFormat = (time: string): boolean => {
|
|
if (!time.match(/^[0-9]{2}:[0-9]{2}$/)) {
|
|
return false;
|
|
}
|
|
const [hours, minutes] = time.split(":").map(Number);
|
|
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const validateWindowLength = (
|
|
formData: ISoftwareAutoUpdateConfigFormData,
|
|
validations?: ISoftwareAutoUpdateConfigFormValidation
|
|
) => {
|
|
if (
|
|
formData.autoUpdateStartTime.length === 0 ||
|
|
formData.autoUpdateEndTime.length === 0 ||
|
|
!validations?.autoUpdateStartTime ||
|
|
!validations.autoUpdateStartTime.isValid ||
|
|
!validations.autoUpdateEndTime ||
|
|
!validations.autoUpdateEndTime.isValid
|
|
) {
|
|
return true; // Skip this validation if startTime is invalid
|
|
}
|
|
const [startHours, startMinutes] = formData.autoUpdateStartTime
|
|
.split(":")
|
|
.map(Number);
|
|
const [endHours, endMinutes] = formData.autoUpdateEndTime
|
|
.split(":")
|
|
.map(Number);
|
|
const startTotalMinutes = startHours * 60 + startMinutes;
|
|
const endTotalMinutes = endHours * 60 + endMinutes;
|
|
return (
|
|
endTotalMinutes < startTotalMinutes ||
|
|
endTotalMinutes - startTotalMinutes >= 60
|
|
);
|
|
};
|
|
|
|
const FORM_VALIDATIONS: IFormValidations = {
|
|
autoUpdateStartTime: {
|
|
validations: [
|
|
{
|
|
name: "required",
|
|
isValid: (formData: ISoftwareAutoUpdateConfigFormData) => {
|
|
return formData.autoUpdateStartTime.length > 0;
|
|
},
|
|
message: `Earliest start time is required`,
|
|
},
|
|
{
|
|
name: "valid",
|
|
isValid: (formData: ISoftwareAutoUpdateConfigFormData) => {
|
|
if (formData.autoUpdateStartTime.length === 0) {
|
|
return true; // Skip this validation if startTime is empty
|
|
}
|
|
return validateTimeFormat(formData.autoUpdateStartTime);
|
|
},
|
|
message: `Use HH:MM format (24-hour clock)`,
|
|
},
|
|
],
|
|
},
|
|
autoUpdateEndTime: {
|
|
validations: [
|
|
{
|
|
name: "required",
|
|
isValid: (formData: ISoftwareAutoUpdateConfigFormData) => {
|
|
return formData.autoUpdateEndTime.length > 0;
|
|
},
|
|
message: `Latest start time is required`,
|
|
},
|
|
{
|
|
name: "valid",
|
|
isValid: (formData: ISoftwareAutoUpdateConfigFormData) => {
|
|
if (formData.autoUpdateEndTime.length === 0) {
|
|
return true; // Skip this validation if endTime is empty
|
|
}
|
|
return validateTimeFormat(formData.autoUpdateEndTime);
|
|
},
|
|
message: `Use HH:MM format (24-hour clock)`,
|
|
},
|
|
],
|
|
},
|
|
targets: {
|
|
validations: [
|
|
{
|
|
name: "custom_labels_selected",
|
|
isValid: (formData: ISoftwareAutoUpdateConfigFormData) => {
|
|
return (
|
|
formData.targetType !== "Custom" ||
|
|
Object.values(formData.labelTargets).filter((v) => v).length > 0
|
|
);
|
|
},
|
|
message: `At least one label target must be selected`,
|
|
},
|
|
],
|
|
},
|
|
windowLength: {
|
|
validations: [
|
|
{
|
|
name: "minimum_length",
|
|
isValid: validateWindowLength,
|
|
message: `Update window must be at least 60 minutes long`,
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const getErrorMessage = (
|
|
formData: ISoftwareAutoUpdateConfigFormData,
|
|
message?: IValidationMessage
|
|
) => {
|
|
if (message === undefined || typeof message === "string") {
|
|
return message;
|
|
}
|
|
return message(formData);
|
|
};
|
|
|
|
export const validateFormData = (
|
|
formData: ISoftwareAutoUpdateConfigFormData,
|
|
isSaving = false
|
|
) => {
|
|
const formValidation: ISoftwareAutoUpdateConfigFormValidation = {
|
|
isValid: true,
|
|
};
|
|
// If auto updates are not enabled, skip further validations.
|
|
Object.keys(FORM_VALIDATIONS).forEach((key) => {
|
|
if (!formData.autoUpdateEnabled && key !== "targets") {
|
|
return;
|
|
}
|
|
const objKey = key as keyof typeof FORM_VALIDATIONS;
|
|
const failedValidation = FORM_VALIDATIONS[objKey].validations.find(
|
|
(validation) => {
|
|
if (!isSaving && validation.name === "required") {
|
|
return false; // Skip this validation if not saving
|
|
}
|
|
return !validation.isValid(formData, formValidation);
|
|
}
|
|
);
|
|
|
|
if (!failedValidation) {
|
|
formValidation[objKey] = {
|
|
isValid: true,
|
|
};
|
|
} else {
|
|
formValidation.isValid = false;
|
|
formValidation[objKey] = {
|
|
isValid: false,
|
|
message: getErrorMessage(formData, failedValidation.message),
|
|
};
|
|
}
|
|
});
|
|
|
|
return formValidation;
|
|
};
|