mirror of
https://github.com/pyrohost/pyrodactyl.git
synced 2026-04-06 04:01:58 +02:00
Added a cronjob translator (cronstrue) to the scheduler popup window
This allows the user to quick and easy see what the cronjob will do without going to third party websites.
This commit is contained in:
@@ -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",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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 }) => (
|
||||
<Form>
|
||||
<FlashMessageRender byKey={'schedule:edit'} />
|
||||
<Field
|
||||
name={'name'}
|
||||
label={'Schedule name'}
|
||||
description={'A human readable identifier for this schedule.'}
|
||||
/>
|
||||
<div className={`grid grid-cols-2 sm:grid-cols-5 gap-4 mt-6`}>
|
||||
<Field name={'minute'} label={'Minute'} />
|
||||
<Field name={'hour'} label={'Hour'} />
|
||||
<Field name={'dayOfWeek'} label={'Day of week'} />
|
||||
<Field name={'dayOfMonth'} label={'Day of month'} />
|
||||
<Field name={'month'} label={'Month'} />
|
||||
</div>
|
||||
{({ isSubmitting, values }) => {
|
||||
const cronDescription = getCronDescription(
|
||||
values.minute,
|
||||
values.hour,
|
||||
values.dayOfMonth,
|
||||
values.month,
|
||||
values.dayOfWeek,
|
||||
);
|
||||
|
||||
<p className={`text-zinc-400 text-xs mt-2`}>
|
||||
The schedule system uses Cronjob syntax when defining when tasks should begin running. Use the
|
||||
fields above to specify when these tasks should begin running.
|
||||
</p>
|
||||
return (
|
||||
<Form>
|
||||
<FlashMessageRender byKey={'schedule:edit'} />
|
||||
<Field
|
||||
name={'name'}
|
||||
label={'Schedule name'}
|
||||
description={'A human readable identifier for this schedule.'}
|
||||
/>
|
||||
<div className={`grid grid-cols-2 sm:grid-cols-5 gap-4 mt-6`}>
|
||||
<Field name={'minute'} label={'Minute'} />
|
||||
<Field name={'hour'} label={'Hour'} />
|
||||
<Field name={'dayOfWeek'} label={'Day of week'} />
|
||||
<Field name={'dayOfMonth'} label={'Day of month'} />
|
||||
<Field name={'month'} label={'Month'} />
|
||||
</div>
|
||||
|
||||
{timezoneInfo.isDifferent && (
|
||||
<div className={'bg-blue-900/20 border border-blue-400/30 rounded-lg p-4 my-2'}>
|
||||
<div className={'flex items-start gap-3'}>
|
||||
<TriangleExclamation
|
||||
width={22}
|
||||
height={22}
|
||||
fill='currentColor'
|
||||
className={'text-blue-400 mt-0.5 flex-shrink-0 h-5 w-5'}
|
||||
/>
|
||||
<div className={'text-sm'}>
|
||||
<p className={'text-blue-100 font-medium mb-1'}>Timezone Information</p>
|
||||
<p className={'text-blue-200/80 text-xs mb-2'}>
|
||||
Times shown here are configured for the server timezone.
|
||||
{timezoneInfo.difference !== 'same time' && (
|
||||
<span className={'text-blue-100 font-medium'}>
|
||||
{' '}
|
||||
The server is {timezoneInfo.difference} your timezone.
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<div className={'mt-2 text-xs space-y-1'}>
|
||||
<div className={'text-blue-200/60'}>
|
||||
Your timezone:
|
||||
<span className={'font-mono'}>
|
||||
{' '}
|
||||
{formatTimezoneDisplay(
|
||||
timezoneInfo.user.timezone,
|
||||
timezoneInfo.user.offset,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={'text-blue-200/60'}>
|
||||
Server timezone:
|
||||
<span className={'font-mono'}>
|
||||
{' '}
|
||||
{formatTimezoneDisplay(
|
||||
timezoneInfo.server.timezone,
|
||||
timezoneInfo.server.offset,
|
||||
)}
|
||||
</span>
|
||||
<div className={`mt-3 p-3 rounded-lg bg-zinc-800/50 border border-zinc-700/50`}>
|
||||
<p className={`text-sm text-zinc-200 font-medium`}>{cronDescription}</p>
|
||||
</div>
|
||||
|
||||
<p className={`text-zinc-400 text-xs mt-2`}>
|
||||
The schedule system uses Cronjob syntax when defining when tasks should begin running. Use
|
||||
the fields above to specify when these tasks should begin running.
|
||||
</p>
|
||||
|
||||
{timezoneInfo.isDifferent && (
|
||||
<div className={'bg-blue-900/20 border border-blue-400/30 rounded-lg p-4 my-2'}>
|
||||
<div className={'flex items-start gap-3'}>
|
||||
<TriangleExclamation
|
||||
width={22}
|
||||
height={22}
|
||||
fill='currentColor'
|
||||
className={'text-blue-400 mt-0.5 flex-shrink-0 h-5 w-5'}
|
||||
/>
|
||||
<div className={'text-sm'}>
|
||||
<p className={'text-blue-100 font-medium mb-1'}>Timezone Information</p>
|
||||
<p className={'text-blue-200/80 text-xs mb-2'}>
|
||||
Times shown here are configured for the server timezone.
|
||||
{timezoneInfo.difference !== 'same time' && (
|
||||
<span className={'text-blue-100 font-medium'}>
|
||||
{' '}
|
||||
The server is {timezoneInfo.difference} your timezone.
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<div className={'mt-2 text-xs space-y-1'}>
|
||||
<div className={'text-blue-200/60'}>
|
||||
Your timezone:
|
||||
<span className={'font-mono'}>
|
||||
{' '}
|
||||
{formatTimezoneDisplay(
|
||||
timezoneInfo.user.timezone,
|
||||
timezoneInfo.user.offset,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={'text-blue-200/60'}>
|
||||
Server timezone:
|
||||
<span className={'font-mono'}>
|
||||
{' '}
|
||||
{formatTimezoneDisplay(
|
||||
timezoneInfo.server.timezone,
|
||||
timezoneInfo.server.offset,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
<div className='gap-3 my-6 flex flex-col'>
|
||||
<a href='https://crontab.guru/' target='_blank' rel='noreferrer'>
|
||||
<ItemContainer
|
||||
description={'Online editor for cron schedule experessions.'}
|
||||
title={'Crontab Guru'}
|
||||
// defaultChecked={showCheatsheet}
|
||||
// onChange={() => setShowCheetsheet((s) => !s)}
|
||||
labelClasses='cursor-pointer'
|
||||
>
|
||||
<Link width={22} height={22} fill='currentColor' className={`px-5 h-5 w-5`} />
|
||||
</ItemContainer>
|
||||
</a>
|
||||
{/* This table would be pretty awkward to make look nice
|
||||
<div className='gap-3 my-6 flex flex-col'>
|
||||
<a href='https://crontab.guru/' target='_blank' rel='noreferrer'>
|
||||
<ItemContainer
|
||||
description={'Online editor for cron schedule experessions.'}
|
||||
title={'Crontab Guru'}
|
||||
// defaultChecked={showCheatsheet}
|
||||
// onChange={() => setShowCheetsheet((s) => !s)}
|
||||
labelClasses='cursor-pointer'
|
||||
>
|
||||
<Link width={22} height={22} fill='currentColor' className={`px-5 h-5 w-5`} />
|
||||
</ItemContainer>
|
||||
</a>
|
||||
{/* This table would be pretty awkward to make look nice
|
||||
Maybe there could be an element for a dropdown later? */}
|
||||
{/* {showCheatsheet && (
|
||||
{/* {showCheatsheet && (
|
||||
<div className={`block md:flex w-full`}>
|
||||
<ScheduleCheatsheetCards />
|
||||
</div>
|
||||
)} */}
|
||||
<FormikSwitchV2
|
||||
name={'onlyWhenOnline'}
|
||||
description={'Only execute this schedule when the server is running.'}
|
||||
label={'Only When Server Is Online'}
|
||||
/>
|
||||
<FormikSwitchV2
|
||||
name={'enabled'}
|
||||
description={'This schedule will be executed automatically if enabled.'}
|
||||
label={'Schedule Enabled'}
|
||||
/>
|
||||
</div>
|
||||
<div className={`mb-6 text-right`}>
|
||||
<ActionButton
|
||||
variant='primary'
|
||||
className={'w-full sm:w-auto'}
|
||||
type={'submit'}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{schedule ? 'Save changes' : 'Create schedule'}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
<FormikSwitchV2
|
||||
name={'onlyWhenOnline'}
|
||||
description={'Only execute this schedule when the server is running.'}
|
||||
label={'Only When Server Is Online'}
|
||||
/>
|
||||
<FormikSwitchV2
|
||||
name={'enabled'}
|
||||
description={'This schedule will be executed automatically if enabled.'}
|
||||
label={'Schedule Enabled'}
|
||||
/>
|
||||
</div>
|
||||
<div className={`mb-6 text-right`}>
|
||||
<ActionButton
|
||||
variant='primary'
|
||||
className={'w-full sm:w-auto'}
|
||||
type={'submit'}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{schedule ? 'Save changes' : 'Create schedule'}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user