feat: validate cron syntax

This commit is contained in:
2026-07-10 10:43:57 +02:00
parent ef5d8b2bbd
commit 900d2ea714
3 changed files with 28 additions and 7 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
## TODO ## TODO
- [ ] auto refresh task completion - [ ] auto refresh task completion
- [ ] validate cron string - [x] validate cron string
- [ ] clone task - [ ] clone task
- [ ] delete task - [ ] delete task
- [ ] list per URL - [ ] list per URL
+20 -3
View File
@@ -1,7 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onBeforeMount, watch } from "vue"; import { ref, onBeforeMount, watch } from "vue";
const cron = defineModel<string|null>({ required: true }); import { CronExpressionParser } from 'cron-parser'
const cron = defineModel<string|null>({ required: true });
const rawCron = ref<string>('0 0 * * *');
const valid = ref<boolean>(true);
const props = defineProps<{editMode: boolean}>(); const props = defineProps<{editMode: boolean}>();
enum CRON_OPTION { enum CRON_OPTION {
@@ -35,6 +38,7 @@ function updateFromValue() {
} else { } else {
cronOption.value = CRON_OPTION.SPECIFIC; cronOption.value = CRON_OPTION.SPECIFIC;
} }
rawCron.value = cron.value ?? '0 0 * * *';
} }
function updateCron() { function updateCron() {
@@ -55,7 +59,7 @@ function updateCron() {
cron.value = `0 0 1 ${cronValue.value.toFixed(0)} *`; cron.value = `0 0 1 ${cronValue.value.toFixed(0)} *`;
break; break;
case CRON_OPTION.SPECIFIC: case CRON_OPTION.SPECIFIC:
cron.value ??= '0 0 * * *'; rawCron.value = cron.value ?? '0 0 * * *';
break; break;
} }
} }
@@ -70,6 +74,9 @@ function changecronOption() {
case CRON_OPTION.EVERY_YEAR: case CRON_OPTION.EVERY_YEAR:
cronValue.value = 1; cronValue.value = 1;
break; break;
case CRON_OPTION.SPECIFIC:
cron.value ??= '0 0 * * *';
break;
} }
updateCron(); updateCron();
} }
@@ -82,6 +89,16 @@ watch(cron, () => {
}); });
watch(cronValue, updateCron); watch(cronValue, updateCron);
watch(rawCron, () => {
try {
CronExpressionParser.parse(rawCron.value);
valid.value = true;
cron.value = rawCron.value;
} catch {
valid.value = false;
}
});
</script> </script>
<template> <template>
@@ -123,6 +140,6 @@ watch(cronValue, updateCron);
<option :value="11">November</option> <option :value="11">November</option>
<option :value="12">December</option> <option :value="12">December</option>
</select> </select>
<input v-if="cronOption === CRON_OPTION.SPECIFIC" v-model="cron" type="text" class="input font-mono" /> <input v-if="cronOption === CRON_OPTION.SPECIFIC" v-model="rawCron" type="text" class="input font-mono" :class="valid ? '' : 'input-error'" />
</div> </div>
</template> </template>
+7 -3
View File
@@ -18,9 +18,13 @@ const checked = computed<boolean>(() => {
if (task.value.reset_cron === null) { if (task.value.reset_cron === null) {
return true; return true;
} }
const interval = CronExpressionParser.parse(task.value.reset_cron, {currentDate: task.value.check_date}); try {
const nextDate = interval.next(); const interval = CronExpressionParser.parse(task.value.reset_cron, {currentDate: task.value.check_date});
return nextDate.getTime() > (new Date()).getTime(); const nextDate = interval.next();
return nextDate.getTime() > (new Date()).getTime();
} catch {
return false;
}
}); });
function save() { function save() {