diff --git a/package.json b/package.json index 4155a5b0b..6a65b19c8 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "copy-to-clipboard": "^3.3.3", + "cronstrue": "^3.9.0", "date-fns": "^4.1.0", "debounce": "^2.2.0", "deepmerge-ts": "^7.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7973ec5e6..78b07df0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,6 +113,9 @@ importers: copy-to-clipboard: specifier: ^3.3.3 version: 3.3.3 + cronstrue: + specifier: ^3.9.0 + version: 3.9.0 date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -2269,6 +2272,10 @@ packages: crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cronstrue@3.9.0: + resolution: {integrity: sha512-T3S35zmD0Ai2B4ko6+mEM+k9C6tipe2nB9RLiGT6QL2Wn0Vsn2cCZAC8Oeuf4CaE00GZWVdpYitbpWCNlIWqdA==} + hasBin: true + cross-env@10.0.0: resolution: {integrity: sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==} engines: {node: '>=20'} @@ -6006,6 +6013,8 @@ snapshots: crelt@1.0.6: {} + cronstrue@3.9.0: {} + cross-env@10.0.0: dependencies: '@epic-web/invariant': 1.0.0 diff --git a/resources/scripts/components/server/schedules/EditScheduleModal.tsx b/resources/scripts/components/server/schedules/EditScheduleModal.tsx index 88d86eab6..c697207a5 100644 --- a/resources/scripts/components/server/schedules/EditScheduleModal.tsx +++ b/resources/scripts/components/server/schedules/EditScheduleModal.tsx @@ -1,6 +1,7 @@ import ModalContext from '@/context/ModalContext'; import { TZDate } from '@date-fns/tz'; import { Link, TriangleExclamation } from '@gravity-ui/icons'; +import { toString } from 'cronstrue'; import { format } from 'date-fns'; import { useStoreState } from 'easy-peasy'; import { Form, Formik, FormikHelpers } from 'formik'; @@ -101,6 +102,35 @@ const formatTimezoneDisplay = (timezone: string, offset: string) => { return `${timezone} (${offset})`; }; +const getCronDescription = ( + minute: string, + hour: string, + dayOfMonth: string, + month: string, + dayOfWeek: string, +): string => { + try { + // Build cron expression: minute hour dayOfMonth month dayOfWeek + const cronExpression = `${minute} ${hour} ${dayOfMonth} ${month} ${dayOfWeek}`; + const description = toString(cronExpression, { + throwExceptionOnParseError: false, + verbose: true, + }); + + // Check if cronstrue returned an error message + if ( + description === + 'An error occurred when generating the expression description. Check the cron expression syntax.' + ) { + return 'Invalid cron expression'; + } + + return description; + } catch { + return 'Invalid cron expression.'; + } +}; + const EditScheduleModal = ({ schedule }: Props) => { const { addError, clearFlashes } = useFlash(); const { dismiss, setPropOverrides } = useContext(ModalContext); @@ -167,116 +197,130 @@ const EditScheduleModal = ({ schedule }: Props) => { } as Values } > - {({ isSubmitting }) => ( -