diff --git a/components.d.ts b/components.d.ts index 3e65c3cc..d77d814b 100644 --- a/components.d.ts +++ b/components.d.ts @@ -135,13 +135,18 @@ declare module '@vue/runtime-core' { NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] + NForm: typeof import('naive-ui')['NForm'] + NFormItem: typeof import('naive-ui')['NFormItem'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] + NRadio: typeof import('naive-ui')['NRadio'] + NRadioGroup: typeof import('naive-ui')['NRadioGroup'] NSpace: typeof import('naive-ui')['NSpace'] + NSwitch: typeof import('naive-ui')['NSwitch'] NTable: typeof import('naive-ui')['NTable'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] diff --git a/src/tools/crontab-generator/crontab-generator.service.test.ts b/src/tools/crontab-generator/crontab-generator.service.test.ts index 388a92bf..d45fadfe 100644 --- a/src/tools/crontab-generator/crontab-generator.service.test.ts +++ b/src/tools/crontab-generator/crontab-generator.service.test.ts @@ -4,6 +4,7 @@ import { getCronType, getLastExecutionTimes, isCronValid } from './crontab-gener describe('crontab-generator', () => { describe('isCronValid', () => { it('should return true for all valid formats', () => { + // standard format expect(isCronValid('0 0 * * 1-5')).toBe(true); expect(isCronValid('23 0-20/2 * * *')).toBe(true); @@ -11,6 +12,24 @@ describe('crontab-generator', () => { expect(isCronValid('0 11-22 ? * MON-FRI *')).toBe(true); expect(isCronValid('0 0 ? * 1 *')).toBe(true); }); + it('should check standard format', () => { + // standard format + expect(isCronValid('0 0 * * 1-5', 'standard')).toBe(true); + expect(isCronValid('23 0-20/2 * * *', 'standard')).toBe(true); + + // AWS format + expect(isCronValid('0 11-22 ? * MON-FRI *', 'standard')).toBe(false); + expect(isCronValid('0 0 ? * 1 *', 'standard')).toBe(false); + }); + it('should check aws format', () => { + // standard format + expect(isCronValid('0 0 * * 1-5', 'aws')).toBe(false); + expect(isCronValid('23 0-20/2 * * *', 'aws')).toBe(false); + + // AWS format + expect(isCronValid('0 11-22 ? * MON-FRI *', 'aws')).toBe(true); + expect(isCronValid('0 0 ? * 1 *', 'aws')).toBe(true); + }); it('should return false for all invalid formats', () => { expect(isCronValid('aert')).toBe(false); expect(isCronValid('40 *')).toBe(false); diff --git a/src/tools/crontab-generator/crontab-generator.service.ts b/src/tools/crontab-generator/crontab-generator.service.ts index 1d44405b..8e4b7b7e 100644 --- a/src/tools/crontab-generator/crontab-generator.service.ts +++ b/src/tools/crontab-generator/crontab-generator.service.ts @@ -1,6 +1,8 @@ import { parseExpression } from 'cron-parser'; import EventCronParser from 'event-cron-parser'; +export type CronType = 'standard' | 'aws'; + export function getLastExecutionTimes(cronExpression: string, tz: string | undefined = undefined, count: number = 5) { if (getCronType(cronExpression) === 'standard') { const interval = parseExpression(cronExpression, { tz }); @@ -22,18 +24,19 @@ export function getLastExecutionTimes(cronExpression: string, tz: string | undef return []; } -export function isCronValid(v: string) { - return !!getCronType(v); +export function isCronValid(cronExpression: string, cronType: CronType | 'any' = 'any') { + const expressionCronType = getCronType(cronExpression); + return cronType === 'any' ? !!expressionCronType : expressionCronType === cronType; } -export function getCronType(v: string) { +export function getCronType(cronExpression: string) { try { - parseExpression(v); + parseExpression(cronExpression); return 'standard'; } catch (_) { try { - const parsed = new EventCronParser(v); + const parsed = new EventCronParser(cronExpression); parsed.validate(); return 'aws'; } diff --git a/src/tools/crontab-generator/crontab-generator.vue b/src/tools/crontab-generator/crontab-generator.vue index 654e278a..4dc48123 100644 --- a/src/tools/crontab-generator/crontab-generator.vue +++ b/src/tools/crontab-generator/crontab-generator.vue @@ -2,7 +2,7 @@ import cronstrue from 'cronstrue'; import ctz from 'countries-and-timezones'; import getTimezoneOffset from 'get-timezone-offset'; -import { getCronType, getLastExecutionTimes, isCronValid } from './crontab-generator.service'; +import { type CronType, getLastExecutionTimes, isCronValid } from './crontab-generator.service'; import { useStyleStore } from '@/stores/style.store'; import { useQueryParamOrStorage } from '@/composable/queryParams'; @@ -20,10 +20,13 @@ const cronstrueConfig = reactive({ // getTimezoneOffset(tz.name, now) / 60 const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; -const allTimezones = Object.values(ctz.getAllTimezones()).map(tz => ({ - value: tz.name, - label: `${tz.name === browserTimezone ? 'Browser TZ - ' : ''}${tz.name} (${tz.utcOffset === tz.dstOffset ? tz.utcOffsetStr : `${tz.utcOffsetStr}/${tz.dstOffsetStr}`})`, -})); +const allTimezones = Object.values(ctz.getAllTimezones()).map((tz) => { + const timezoneUTCDSTOffset = tz.utcOffset === tz.dstOffset ? tz.utcOffsetStr : `${tz.utcOffsetStr}/${tz.dstOffsetStr}`; + return { + value: tz.name, + label: `${tz.name === browserTimezone ? 'Browser TZ - ' : ''}${tz.name} (${timezoneUTCDSTOffset})`, + }; +}); const currentTimezone = useQueryParamOrStorage({ name: 'tz', storageName: 'crongen:tz', defaultValue: browserTimezone }); watchEffect(() => { cronstrueConfig.tzOffset = -getTimezoneOffset(currentTimezone.value, new Date()) / 60; @@ -136,19 +139,24 @@ const awsHelpers = [ }, ]; -const cronType = computed({ - get() { - return getCronType(cron.value); - }, - set(newCronType) { +const defaultAWSCronExpression = '0 0 ? * 1 *'; +const defaultStandardCronExpression = '40 * * * *'; +const cronType = ref('standard'); +watch(cronType, + (newCronType) => { if (newCronType === 'aws') { - cron.value = '0 0 ? * 1 *'; + if (!cron.value || cron.value === defaultStandardCronExpression) { + cron.value = defaultAWSCronExpression; + } } - else { - cron.value = '40 * * * *'; + else if (newCronType === 'standard') { + if (!cron.value || cron.value === defaultAWSCronExpression) { + cron.value = defaultStandardCronExpression; + } } }, -}); +); + const getHelpers = computed(() => { if (cronType.value === 'aws') { return awsHelpers; @@ -165,7 +173,7 @@ const cronString = computed(() => { const cronValidationRules = [ { - validator: (value: string) => isCronValid(value), + validator: (value: string) => isCronValid(value, cronType.value), message: 'This cron is invalid', }, ]; @@ -245,7 +253,7 @@ const executionTimesString = computed(() => {
--- Standard CRON Syntax --
+      -- Standard CRON Syntax --
 ┌──────────── [optional] seconds (0 - 59)
 | ┌────────── minute (0 - 59)
 | | ┌──────── hour (0 - 23)
@@ -256,7 +264,7 @@ const executionTimesString = computed(() => {
 * * * * * * command
--- AWS CRON Syntax --
+      -- AWS CRON Syntax --
 ┌──────────── minute (0 - 59)
 | ┌────────── hour (0 - 23)
 | | ┌──────── day of month (1 - 31) OR ? OR L OR W