CronEditor.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <script setup lang="ts">
  2. const modelValue = defineModel<string>({ default: '' })
  3. interface CronConfig {
  4. type: 'daily' | 'weekly' | 'monthly' | 'custom'
  5. hour: number
  6. minute: number
  7. dayOfWeek?: number // 0-6, 0 = Sunday
  8. dayOfMonth?: number // 1-31
  9. }
  10. const cronConfig = ref<CronConfig>({
  11. type: 'daily',
  12. hour: 0,
  13. minute: 0,
  14. })
  15. const cronTypes = [
  16. { label: $gettext('Daily'), value: 'daily' },
  17. { label: $gettext('Weekly'), value: 'weekly' },
  18. { label: $gettext('Monthly'), value: 'monthly' },
  19. // { label: $gettext('Custom'), value: 'custom' },
  20. ]
  21. const weekDays = [
  22. { label: $gettext('Sunday'), value: 0 },
  23. { label: $gettext('Monday'), value: 1 },
  24. { label: $gettext('Tuesday'), value: 2 },
  25. { label: $gettext('Wednesday'), value: 3 },
  26. { label: $gettext('Thursday'), value: 4 },
  27. { label: $gettext('Friday'), value: 5 },
  28. { label: $gettext('Saturday'), value: 6 },
  29. ]
  30. const customCronExpression = ref('')
  31. // Parse cron expression to config
  32. function parseCronExpression(cron: string) {
  33. if (!cron)
  34. return
  35. const parts = cron.trim().split(/\s+/)
  36. if (parts.length !== 5) {
  37. cronConfig.value.type = 'custom'
  38. customCronExpression.value = cron
  39. return
  40. }
  41. const [minute, hour, dayOfMonth, month, dayOfWeek] = parts
  42. cronConfig.value.minute = Number.parseInt(minute) || 0
  43. cronConfig.value.hour = Number.parseInt(hour) || 0
  44. // Check if it's a daily pattern (every day)
  45. if (dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
  46. cronConfig.value.type = 'daily'
  47. return
  48. }
  49. // Check if it's a weekly pattern (specific day of week)
  50. if (dayOfMonth === '*' && month === '*' && dayOfWeek !== '*') {
  51. cronConfig.value.type = 'weekly'
  52. cronConfig.value.dayOfWeek = Number.parseInt(dayOfWeek) || 0
  53. return
  54. }
  55. // Check if it's a monthly pattern (specific day of month)
  56. if (dayOfMonth !== '*' && month === '*' && dayOfWeek === '*') {
  57. cronConfig.value.type = 'monthly'
  58. cronConfig.value.dayOfMonth = Number.parseInt(dayOfMonth) || 1
  59. return
  60. }
  61. // Otherwise, it's custom
  62. cronConfig.value.type = 'custom'
  63. customCronExpression.value = cron
  64. }
  65. // Generate cron expression from config
  66. function generateCronExpression() {
  67. const { type, minute, hour, dayOfWeek, dayOfMonth } = cronConfig.value
  68. switch (type) {
  69. case 'daily':
  70. return `${minute} ${hour} * * *`
  71. case 'weekly':
  72. return `${minute} ${hour} * * ${dayOfWeek ?? 0}`
  73. case 'monthly':
  74. return `${minute} ${hour} ${dayOfMonth ?? 1} * *`
  75. case 'custom':
  76. return customCronExpression.value
  77. default:
  78. return `${minute} ${hour} * * *`
  79. }
  80. }
  81. // Watch for changes and update model value
  82. watch(cronConfig, () => {
  83. if (cronConfig.value.type !== 'custom') {
  84. modelValue.value = generateCronExpression()
  85. }
  86. }, { deep: true })
  87. watch(customCronExpression, newValue => {
  88. if (cronConfig.value.type === 'custom') {
  89. modelValue.value = newValue
  90. }
  91. })
  92. // Initialize from model value
  93. watch(modelValue, newValue => {
  94. if (newValue) {
  95. parseCronExpression(newValue)
  96. }
  97. }, { immediate: true })
  98. // Human readable description
  99. const cronDescription = computed(() => {
  100. const { type, hour, minute, dayOfWeek, dayOfMonth } = cronConfig.value
  101. const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
  102. const dayName = weekDays.find(d => d.value === dayOfWeek)?.label || $gettext('Sunday')
  103. switch (type) {
  104. case 'daily':
  105. return $gettext('Execute on every day at %{time}', { time: timeStr })
  106. case 'weekly':
  107. return $gettext('Execute on every %{day} at %{time}', { day: dayName, time: timeStr })
  108. case 'monthly':
  109. return $gettext('Execute on every month on day %{day} at %{time}', { day: dayOfMonth?.toString() || '1', time: timeStr })
  110. case 'custom':
  111. return customCronExpression.value || $gettext('Custom cron expression')
  112. default:
  113. return ''
  114. }
  115. })
  116. </script>
  117. <template>
  118. <div>
  119. <div class="font-500 mb-4">
  120. {{ $gettext('Backup Schedule') }}
  121. </div>
  122. <AFormItem :label="$gettext('Schedule Type')">
  123. <ASelect v-model:value="cronConfig.type" :options="cronTypes" />
  124. </AFormItem>
  125. <AAlert
  126. v-if="cronDescription"
  127. :message="cronDescription"
  128. type="info"
  129. show-icon
  130. class="mb-4"
  131. />
  132. <template v-if="cronConfig.type !== 'custom'">
  133. <ARow :gutter="16">
  134. <ACol :span="12">
  135. <AFormItem :label="$gettext('Hour')">
  136. <AInputNumber
  137. v-model:value="cronConfig.hour"
  138. :min="0"
  139. :max="23"
  140. style="width: 100%"
  141. />
  142. </AFormItem>
  143. </ACol>
  144. <ACol :span="12">
  145. <AFormItem :label="$gettext('Minute')">
  146. <AInputNumber
  147. v-model:value="cronConfig.minute"
  148. :min="0"
  149. :max="59"
  150. style="width: 100%"
  151. />
  152. </AFormItem>
  153. </ACol>
  154. </ARow>
  155. <AFormItem v-if="cronConfig.type === 'weekly'" :label="$gettext('Day of Week')">
  156. <ASelect v-model:value="cronConfig.dayOfWeek" :options="weekDays" />
  157. </AFormItem>
  158. <AFormItem v-if="cronConfig.type === 'monthly'" :label="$gettext('Day of Month')">
  159. <AInputNumber
  160. v-model:value="cronConfig.dayOfMonth"
  161. :min="1"
  162. :max="31"
  163. style="width: 100%"
  164. />
  165. </AFormItem>
  166. </template>
  167. <AFormItem v-if="cronConfig.type === 'custom'" :label="$gettext('Cron Expression')">
  168. <AInput
  169. v-model:value="customCronExpression"
  170. :placeholder="$gettext('e.g., 0 0 * * * (daily at midnight)')"
  171. />
  172. <div class="mt-2 text-gray-500 text-sm">
  173. {{ $gettext('Format: minute hour day month weekday') }}
  174. </div>
  175. </AFormItem>
  176. </div>
  177. </template>
  178. <style scoped lang="less">
  179. </style>