This commit is contained in:
sharevb 2024-11-30 23:48:49 +00:00 committed by GitHub
commit f49dc46ed7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 312 additions and 13 deletions

5
components.d.ts vendored
View file

@ -135,13 +135,18 @@ declare module '@vue/runtime-core' {
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NEllipsis: typeof import('naive-ui')['NEllipsis'] NEllipsis: typeof import('naive-ui')['NEllipsis']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NH1: typeof import('naive-ui')['NH1'] NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3'] NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon'] NIcon: typeof import('naive-ui')['NIcon']
NLayout: typeof import('naive-ui')['NLayout'] NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSwitch: typeof import('naive-ui')['NSwitch']
NTable: typeof import('naive-ui')['NTable'] NTable: typeof import('naive-ui')['NTable']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] 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'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']

View file

@ -55,7 +55,9 @@
"change-case": "^4.1.2", "change-case": "^4.1.2",
"colord": "^2.9.3", "colord": "^2.9.3",
"composerize-ts": "^0.6.2", "composerize-ts": "^0.6.2",
"countries-and-timezones": "^3.6.0",
"country-code-lookup": "^0.1.0", "country-code-lookup": "^0.1.0",
"cron-parser": "^4.9.0",
"cron-validator": "^1.3.1", "cron-validator": "^1.3.1",
"cronstrue": "^2.26.0", "cronstrue": "^2.26.0",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
@ -63,9 +65,11 @@
"dompurify": "^3.0.6", "dompurify": "^3.0.6",
"email-normalizer": "^1.0.0", "email-normalizer": "^1.0.0",
"emojilib": "^3.0.10", "emojilib": "^3.0.10",
"event-cron-parser": "^1.0.34",
"figlet": "^1.7.0", "figlet": "^1.7.0",
"figue": "^1.2.0", "figue": "^1.2.0",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"get-timezone-offset": "^1.0.5",
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"iarna-toml-esm": "^3.0.5", "iarna-toml-esm": "^3.0.5",
"ibantools": "^4.3.3", "ibantools": "^4.3.3",

50
pnpm-lock.yaml generated
View file

@ -62,9 +62,15 @@ dependencies:
composerize-ts: composerize-ts:
specifier: ^0.6.2 specifier: ^0.6.2
version: 0.6.2 version: 0.6.2
countries-and-timezones:
specifier: ^3.6.0
version: 3.6.0
country-code-lookup: country-code-lookup:
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0 version: 0.1.0
cron-parser:
specifier: ^4.9.0
version: 4.9.0
cron-validator: cron-validator:
specifier: ^1.3.1 specifier: ^1.3.1
version: 1.3.1 version: 1.3.1
@ -86,6 +92,9 @@ dependencies:
emojilib: emojilib:
specifier: ^3.0.10 specifier: ^3.0.10
version: 3.0.10 version: 3.0.10
event-cron-parser:
specifier: ^1.0.34
version: 1.0.34
figlet: figlet:
specifier: ^1.7.0 specifier: ^1.7.0
version: 1.7.0 version: 1.7.0
@ -95,6 +104,9 @@ dependencies:
fuse.js: fuse.js:
specifier: ^6.6.2 specifier: ^6.6.2
version: 6.6.2 version: 6.6.2
get-timezone-offset:
specifier: ^1.0.5
version: 1.0.5
highlight.js: highlight.js:
specifier: ^11.7.0 specifier: ^11.7.0
version: 11.7.0 version: 11.7.0
@ -3412,7 +3424,7 @@ packages:
dependencies: dependencies:
'@unhead/dom': 0.5.1 '@unhead/dom': 0.5.1
'@unhead/schema': 0.5.1 '@unhead/schema': 0.5.1
'@vueuse/shared': 11.0.3(vue@3.3.4) '@vueuse/shared': 11.1.0(vue@3.3.4)
unhead: 0.5.1 unhead: 0.5.1
vue: 3.3.4 vue: 3.3.4
transitivePeerDependencies: transitivePeerDependencies:
@ -4054,8 +4066,8 @@ packages:
- vue - vue
dev: false dev: false
/@vueuse/shared@11.0.3(vue@3.3.4): /@vueuse/shared@11.1.0(vue@3.3.4):
resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==} resolution: {integrity: sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==}
dependencies: dependencies:
vue-demi: 0.14.10(vue@3.3.4) vue-demi: 0.14.10(vue@3.3.4)
transitivePeerDependencies: transitivePeerDependencies:
@ -4674,6 +4686,11 @@ packages:
browserslist: 4.22.1 browserslist: 4.22.1
dev: true dev: true
/countries-and-timezones@3.6.0:
resolution: {integrity: sha512-8/nHBCs1eKeQ1jnsZVGdqrLYxS8nPcfJn8PnmxdJXWRLZdXsGFR8gnVhRjatGDBjqmPm7H+FtYpBYTPWd0Eiqg==}
engines: {node: '>=8.x', npm: '>=5.x'}
dev: false
/country-code-lookup@0.1.0: /country-code-lookup@0.1.0:
resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==} resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==}
dev: false dev: false
@ -4682,6 +4699,13 @@ packages:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
dev: false dev: false
/cron-parser@4.9.0:
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'}
dependencies:
luxon: 3.5.0
dev: false
/cron-validator@1.3.1: /cron-validator@1.3.1:
resolution: {integrity: sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==} resolution: {integrity: sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==}
dev: false dev: false
@ -5583,6 +5607,12 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/event-cron-parser@1.0.34:
resolution: {integrity: sha512-ytqZmMrNfSvzHWriiHdoNOpYKFr4d2fDoC4Rgq0F8lEA37abCWkYhSsqslC/kngWwnTGq7L0Q9VlMreKe6EJbQ==}
dependencies:
number-to-words: 1.2.4
dev: false
/event-stream@3.3.4: /event-stream@3.3.4:
resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==}
dependencies: dependencies:
@ -5877,6 +5907,11 @@ packages:
get-intrinsic: 1.2.2 get-intrinsic: 1.2.2
dev: true dev: true
/get-timezone-offset@1.0.5:
resolution: {integrity: sha512-+B+/vEJ9qJgZheDVNmuY+4il8sJhTFXRvSiiqyRfwiCEhTaZqn/yCoNToDzQL+Mv9DLKlyO1bSIP5nUCJQN9Aw==}
engines: {node: '>=4.0.0'}
dev: false
/get-tsconfig@4.7.2: /get-tsconfig@4.7.2:
resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
dependencies: dependencies:
@ -6834,6 +6869,11 @@ packages:
dependencies: dependencies:
yallist: 4.0.0 yallist: 4.0.0
/luxon@3.5.0:
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
engines: {node: '>=12'}
dev: false
/magic-string@0.25.9: /magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
dependencies: dependencies:
@ -7203,6 +7243,10 @@ packages:
boolbase: 1.0.0 boolbase: 1.0.0
dev: true dev: true
/number-to-words@1.2.4:
resolution: {integrity: sha512-/fYevVkXRcyBiZDg6yzZbm0RuaD6i0qRfn8yr+6D0KgBMOndFPxuW10qCHpzs50nN8qKuv78k8MuotZhcVX6Pw==}
dev: false
/nwsapi@2.2.7: /nwsapi@2.2.7:
resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
dev: true dev: true

View file

@ -0,0 +1,63 @@
import { describe, expect, it } from 'vitest';
import { getCronType, getLastExecutionTimes, isCronValid } from './crontab-generator.service';
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);
// AWS formats
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);
});
});
describe('getCronType', () => {
it('should return right type', () => {
expect(getCronType('0 0 * * 1-5')).toBe('standard');
expect(getCronType('23 0-20/2 * * *')).toBe('standard');
// AWS formats
expect(getCronType('0 11-22 ? * MON-FRI *')).toBe('aws');
expect(getCronType('0 0 ? * 1 *')).toBe('aws');
expect(getCronType('aert')).toBe(false);
expect(getCronType('40 *')).toBe(false);
});
});
describe('getLastExecutionTimes', () => {
it('should return next valid datetimes', () => {
expect(getLastExecutionTimes('0 0 * * 1-5')).toHaveLength(5);
expect(getLastExecutionTimes('23 0-20/2 * * *')).toHaveLength(5);
// AWS formats
expect(getLastExecutionTimes('0 11-22 ? * MON-FRI *')).toHaveLength(5);
expect(getLastExecutionTimes('0 0 ? * 1 *')).toHaveLength(5);
});
});
});

View file

@ -0,0 +1,47 @@
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 });
const times = [];
for (let i = 0; i < count; i++) {
times.push(interval.next().toJSON());
}
return times;
}
if (getCronType(cronExpression) === 'aws') {
const parsed = new EventCronParser(cronExpression);
const times = [];
for (let i = 0; i < count; i++) {
times.push(JSON.stringify(parsed.next()));
}
return times;
}
return [];
}
export function isCronValid(cronExpression: string, cronType: CronType | 'any' = 'any') {
const expressionCronType = getCronType(cronExpression);
return cronType === 'any' ? !!expressionCronType : expressionCronType === cronType;
}
export function getCronType(cronExpression: string) {
try {
parseExpression(cronExpression);
return 'standard';
}
catch (_) {
try {
const parsed = new EventCronParser(cronExpression);
parsed.validate();
return 'aws';
}
catch (_) {
}
}
return false;
}

View file

@ -1,11 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import cronstrue from 'cronstrue'; import cronstrue from 'cronstrue';
import { isValidCron } from 'cron-validator'; import ctz from 'countries-and-timezones';
import getTimezoneOffset from 'get-timezone-offset';
import { type CronType, getLastExecutionTimes, isCronValid } from './crontab-generator.service';
import { useStyleStore } from '@/stores/style.store'; import { useStyleStore } from '@/stores/style.store';
import { useQueryParamOrStorage } from '@/composable/queryParams';
function isCronValid(v: string) {
return isValidCron(v, { allowBlankDay: true, alias: true, seconds: true });
}
const styleStore = useStyleStore(); const styleStore = useStyleStore();
@ -15,9 +14,25 @@ const cronstrueConfig = reactive({
dayOfWeekStartIndexZero: true, dayOfWeekStartIndexZero: true,
use24HourTimeFormat: true, use24HourTimeFormat: true,
throwExceptionOnParseError: true, throwExceptionOnParseError: true,
monthStartIndexZero: false,
tzOffset: (new Date()).getTimezoneOffset() / 60,
}); });
const helpers = [ // getTimezoneOffset(tz.name, now) / 60
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
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;
});
const commonHelpers = [
{ {
symbol: '*', symbol: '*',
meaning: 'Any value', meaning: 'Any value',
@ -42,6 +57,10 @@ const helpers = [
example: '*/10 * * *', example: '*/10 * * *',
equivalent: 'Every 10 minutes', equivalent: 'Every 10 minutes',
}, },
];
const standardHelpers = [
...commonHelpers,
{ {
symbol: '@yearly', symbol: '@yearly',
meaning: 'Once every year at midnight of 1 January', meaning: 'Once every year at midnight of 1 January',
@ -92,6 +111,59 @@ const helpers = [
}, },
]; ];
const awsHelpers = [
...commonHelpers,
{
symbol: '?',
meaning: 'One or another. In the Day-of-month field you could enter 7, and if you didn\'t care what day of the week the seventh was, you could enter ? in the Day-of-week field',
example: '9 * 7,9,11 5 ? 2021',
equivalent: 'At 9 minutes past the hour, every hour, on day 7, 9, and 11 of the month, only in May, only in 2021',
},
{
symbol: 'L',
meaning: 'The L wildcard in the Day-of-month or Day-of-week fields specifies the last day of the month or week.',
example: '9 * L 5 ? 2019,2020',
equivalent: 'At 9 minutes past the hour, every hour, on the last day of the month, only in May, only in 2019 and 2020',
},
{
symbol: 'W',
meaning: 'The W wildcard in the Day-of-month field specifies a weekday. In the Day-of-month field, 3W specifies the day closest to the third weekday of the month.',
example: '19 4 3W 9 ? 2019,2020',
equivalent: 'At 04:19 AM, on the weekday nearest day 3 of the month, only in September, only in 2019 and 2020',
},
{
symbol: '#',
meaning: 'The # wildcard in the Day-of-week field specifies the nieth weekday of the month. 3#5 specifies the fifth Wednesday of the month',
example: '9 8-20 ? 12 3#5 2019,2020',
equivalent: 'At 9 minutes past the hour, between 08:00 AM and 08:59 PM, on the fifth Wednesday of the month, only in December, only in 2019 and 2020',
},
];
const defaultAWSCronExpression = '0 0 ? * 1 *';
const defaultStandardCronExpression = '40 * * * *';
const cronType = ref<CronType>('standard');
watch(cronType,
(newCronType) => {
if (newCronType === 'aws') {
if (!cron.value || cron.value === defaultStandardCronExpression) {
cron.value = defaultAWSCronExpression;
}
}
else if (newCronType === 'standard') {
if (!cron.value || cron.value === defaultAWSCronExpression) {
cron.value = defaultStandardCronExpression;
}
}
},
);
const getHelpers = computed(() => {
if (cronType.value === 'aws') {
return awsHelpers;
}
return standardHelpers;
});
const cronString = computed(() => { const cronString = computed(() => {
if (isCronValid(cron.value)) { if (isCronValid(cron.value)) {
return cronstrue.toString(cron.value, cronstrueConfig); return cronstrue.toString(cron.value, cronstrueConfig);
@ -101,10 +173,24 @@ const cronString = computed(() => {
const cronValidationRules = [ const cronValidationRules = [
{ {
validator: (value: string) => isCronValid(value), validator: (value: string) => isCronValid(value, cronType.value),
message: 'This cron is invalid', message: 'This cron is invalid',
}, },
]; ];
const executionTimesString = computed(() => {
if (isCronValid(cron.value)) {
try {
const lastExecutionTimes = getLastExecutionTimes(cron.value, currentTimezone.value);
const executionTimesString = lastExecutionTimes.join('\n');
return `Next 5 execution times:\n${executionTimesString}`;
}
catch (e: any) {
return e.toString();
}
}
return ' ';
});
</script> </script>
<template> <template>
@ -119,10 +205,27 @@ const cronValidationRules = [
/> />
</div> </div>
<n-radio-group v-model:value="cronType" name="radiogroup" mb-2 flex justify-center>
<n-space>
<n-radio
value="standard"
label="Unix standard"
/>
<n-radio
value="aws"
label="AWS"
/>
</n-space>
</n-radio-group>
<div class="cron-string"> <div class="cron-string">
{{ cronString }} {{ cronString }}
</div> </div>
<div class="cron-execution-string">
{{ executionTimesString }}
</div>
<n-divider /> <n-divider />
<div flex justify-center> <div flex justify-center>
@ -136,11 +239,21 @@ const cronValidationRules = [
<n-form-item label="Days start at 0"> <n-form-item label="Days start at 0">
<n-switch v-model:value="cronstrueConfig.dayOfWeekStartIndexZero" /> <n-switch v-model:value="cronstrueConfig.dayOfWeekStartIndexZero" />
</n-form-item> </n-form-item>
<n-form-item label="Months start at 0">
<n-switch v-model:value="cronstrueConfig.monthStartIndexZero" />
</n-form-item>
<c-select
v-model:value="currentTimezone"
searchable
label="Timezone:"
:options="allTimezones"
/>
</n-form> </n-form>
</div> </div>
</c-card> </c-card>
<c-card> <c-card>
<pre> <pre v-if="cronType === 'standard'">
-- Standard CRON Syntax --
[optional] seconds (0 - 59) [optional] seconds (0 - 59)
| minute (0 - 59) | minute (0 - 59)
| | hour (0 - 23) | | hour (0 - 23)
@ -150,8 +263,19 @@ const cronValidationRules = [
| | | | | | | | | | | |
* * * * * * command</pre> * * * * * * command</pre>
<pre v-if="cronType === 'aws'">
-- AWS CRON Syntax --
minute (0 - 59)
| hour (0 - 23)
| | day of month (1 - 31) OR ? OR L OR W
| | | month (1 - 12) OR jan,feb,mar,apr ...
| | | | day of week (0 - 6, sunday=0) OR sun,mon OR L ...
| | | | | year
| | | | | |
* * * * * *</pre>
<div v-if="styleStore.isSmallScreen"> <div v-if="styleStore.isSmallScreen">
<c-card v-for="{ symbol, meaning, example, equivalent } in helpers" :key="symbol" mb-3 important:border-none> <c-card v-for="{ symbol, meaning, example, equivalent } in getHelpers" :key="symbol" mb-3 important:border-none>
<div> <div>
Symbol: <strong>{{ symbol }}</strong> Symbol: <strong>{{ symbol }}</strong>
</div> </div>
@ -168,7 +292,7 @@ const cronValidationRules = [
</c-card> </c-card>
</div> </div>
<c-table v-else :data="helpers" /> <c-table v-else :data="getHelpers" />
</c-card> </c-card>
</template> </template>
@ -191,4 +315,12 @@ pre {
overflow: auto; overflow: auto;
padding: 10px 0; padding: 10px 0;
} }
.cron-execution-string{
text-align: center;
font-size: 14px;
opacity: 0.8;
margin: 5px 0 15px;
white-space: pre-wrap;
}
</style> </style>

View file

@ -0,0 +1,3 @@
declare module "get-timezone-offset" {
export default function(timeZoneName: string, date: Date);
}

View file

@ -20,6 +20,7 @@ export const tool = defineTool({
'day', 'day',
'minute', 'minute',
'second', 'second',
'aws',
], ],
component: () => import('./crontab-generator.vue'), component: () => import('./crontab-generator.vue'),
icon: Alarm, icon: Alarm,