mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Cron expression builder advanced view
This commit is contained in:
		
							parent
							
								
									9a57fcad40
								
							
						
					
					
						commit
						0c20988e18
					
				@ -46,12 +46,12 @@ export default {
 | 
				
			|||||||
          id: 'settings',
 | 
					          id: 'settings',
 | 
				
			||||||
          title: 'Settings',
 | 
					          title: 'Settings',
 | 
				
			||||||
          component: 'modals-libraries-library-settings'
 | 
					          component: 'modals-libraries-library-settings'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: 'schedule',
 | 
				
			||||||
 | 
					          title: 'Schedule',
 | 
				
			||||||
 | 
					          component: 'modals-libraries-schedule-scan'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // {
 | 
					 | 
				
			||||||
        //   id: 'schedule',
 | 
					 | 
				
			||||||
        //   title: 'Schedule',
 | 
					 | 
				
			||||||
        //   component: 'modals-libraries-schedule-scan'
 | 
					 | 
				
			||||||
        // }
 | 
					 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      libraryCopy: null
 | 
					      libraryCopy: null
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,10 @@ export default {
 | 
				
			|||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    toggleEnableAutoScan(v) {
 | 
					    toggleEnableAutoScan(v) {
 | 
				
			||||||
      if (!v) this.updatedCron(null)
 | 
					      if (!v) this.updatedCron(null)
 | 
				
			||||||
 | 
					      else if (!this.cronExpression) {
 | 
				
			||||||
 | 
					        this.cronExpression = '0 0 * * 1'
 | 
				
			||||||
 | 
					        this.updatedCron(this.cronExpression)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    updatedCron(expression) {
 | 
					    updatedCron(expression) {
 | 
				
			||||||
      this.$emit('update', {
 | 
					      this.$emit('update', {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,26 +1,39 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="w-full py-2">
 | 
					  <div class="w-full py-2">
 | 
				
			||||||
    <template v-if="!showAdvancedView">
 | 
					    <div class="flex">
 | 
				
			||||||
      <ui-multi-select-dropdown v-model="selectedWeekdays" @input="updateCron" label="Weekdays to run" :items="weekdays" />
 | 
					      <div class="w-28 h-8 rounded-tl-md shadow-md relative border border-black-200 flex items-center justify-center cursor-pointer hover:text-white" :class="!showAdvancedView ? 'text-gray-200 bg-primary hover:bg-opacity-60' : 'text-gray-400 bg-bg hover:bg-primary hover:bg-opacity-20'" @click="showAdvancedView = false">
 | 
				
			||||||
 | 
					        <p class="text-sm">Cron Builder</p>
 | 
				
			||||||
      <div v-show="selectedWeekdays.length" class="flex items-center py-2">
 | 
					 | 
				
			||||||
        <ui-text-input-with-label v-model="selectedHour" @input="updateCron" @blur="hourBlur" type="number" label="Hour" class="max-w-20" />
 | 
					 | 
				
			||||||
        <p class="text-xl px-2 mt-4">:</p>
 | 
					 | 
				
			||||||
        <ui-text-input-with-label v-model="selectedMinute" @input="updateCron" @blur="minuteBlur" type="number" label="Minute" class="max-w-20" />
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="w-28 h-8 rounded-tr-md shadow-md relative border border-black-200 flex items-center justify-center -ml-px cursor-pointer hover:text-white" :class="showAdvancedView ? 'text-gray-200 bg-primary hover:bg-opacity-60' : 'text-gray-400 bg-bg hover:bg-primary hover:bg-opacity-20'" @click="showAdvancedView = true">
 | 
				
			||||||
      <div v-if="description" class="w-full bg-primary bg-opacity-75 rounded-xl p-4 text-center mt-2">
 | 
					        <p class="text-sm">Advanced</p>
 | 
				
			||||||
        <p class="text-lg text-gray-200" v-html="description" />
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </template>
 | 
					    </div>
 | 
				
			||||||
    <template v-else>
 | 
					    <div class="p-4 border border-black-200 rounded-b-md rounded-tr-md -mt-px" style="min-height: 200px">
 | 
				
			||||||
      <p class="px-1 text-sm font-semibold">Cron Expression</p>
 | 
					      <template v-if="!showAdvancedView">
 | 
				
			||||||
      <ui-text-input v-model="customCronExpression" @blur="cronExpressionBlur" label="Cron Expression" :padding-y="1" text-center class="w-full text-4xl -tracking-widest mb-2" />
 | 
					        <ui-multi-select-dropdown v-model="selectedWeekdays" @input="updateCron" label="Weekdays to run" :items="weekdays" />
 | 
				
			||||||
      <ui-btn v-if="!customCronError" small :disabled="isValidating" @click="validateCron">Validate Cron Expression</ui-btn>
 | 
					
 | 
				
			||||||
      <p v-else class="text-error text-xl">{{ customCronError }}</p>
 | 
					        <div v-show="selectedWeekdays.length" class="flex items-center py-2">
 | 
				
			||||||
    </template>
 | 
					          <ui-text-input-with-label v-model="selectedHour" @input="updateCron" @blur="hourBlur" type="number" label="Hour" class="max-w-20" />
 | 
				
			||||||
        <div class="flex justify-end mt-4">
 | 
					          <p class="text-xl px-2 mt-4">:</p>
 | 
				
			||||||
      <ui-checkbox v-model="showAdvancedView" small checkbox-bg="bg" label="Advanced" />
 | 
					          <ui-text-input-with-label v-model="selectedMinute" @input="updateCron" @blur="minuteBlur" type="number" label="Minute" class="max-w-20" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div v-if="description" class="w-full bg-primary bg-opacity-75 rounded-xl p-4 text-center mt-2">
 | 
				
			||||||
 | 
					          <p class="text-lg text-gray-200" v-html="description" />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </template>
 | 
				
			||||||
 | 
					      <template v-else>
 | 
				
			||||||
 | 
					        <p class="px-1 text-sm font-semibold">Cron Expression</p>
 | 
				
			||||||
 | 
					        <ui-text-input v-model="customCronExpression" @blur="cronExpressionBlur" label="Cron Expression" :padding-y="2" text-center class="w-full text-4xl -tracking-widest mb-4 font-mono" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="flex items-center justify-center">
 | 
				
			||||||
 | 
					          <widgets-loading-spinner v-if="isValidating" class="mr-2" />
 | 
				
			||||||
 | 
					          <span v-else class="material-icons-outlined mr-2" :class="isValid ? 'text-success' : 'text-error'">{{ isValid ? 'check_circle_outline' : 'error_outline' }}</span>
 | 
				
			||||||
 | 
					          <p v-if="isValidating" class="text-gray-300 text-lg text-center">Checking cron...</p>
 | 
				
			||||||
 | 
					          <p v-else-if="customCronError" class="text-error text-lg text-center">{{ customCronError }}</p>
 | 
				
			||||||
 | 
					          <p v-else class="text-success text-lg text-center">Valid cron expression</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </template>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@ -42,7 +55,9 @@ export default {
 | 
				
			|||||||
      cronExpression: '0 0 * * *',
 | 
					      cronExpression: '0 0 * * *',
 | 
				
			||||||
      customCronExpression: '0 0 * * *',
 | 
					      customCronExpression: '0 0 * * *',
 | 
				
			||||||
      customCronError: '',
 | 
					      customCronError: '',
 | 
				
			||||||
      isValidating: false
 | 
					      isValidating: false,
 | 
				
			||||||
 | 
					      validatedCron: null,
 | 
				
			||||||
 | 
					      isValid: true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
@ -120,6 +135,9 @@ export default {
 | 
				
			|||||||
      this.selectedWeekdays.sort()
 | 
					      this.selectedWeekdays.sort()
 | 
				
			||||||
      this.cronExpression = `${this.selectedMinute} ${this.selectedHour} * * ${this.selectedWeekdays.join(',')}`
 | 
					      this.cronExpression = `${this.selectedMinute} ${this.selectedHour} * * ${this.selectedWeekdays.join(',')}`
 | 
				
			||||||
      this.customCronExpression = this.cronExpression
 | 
					      this.customCronExpression = this.cronExpression
 | 
				
			||||||
 | 
					      this.validatedCron = this.cronExpression
 | 
				
			||||||
 | 
					      this.isValid = true
 | 
				
			||||||
 | 
					      this.customCronError = ''
 | 
				
			||||||
      this.$emit('input', this.cronExpression)
 | 
					      this.$emit('input', this.cronExpression)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    minuteBlur() {
 | 
					    minuteBlur() {
 | 
				
			||||||
@ -144,13 +162,15 @@ export default {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      this.updateCron()
 | 
					      this.updateCron()
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    simpleValidateCustomCron() {
 | 
					    async cronExpressionBlur() {
 | 
				
			||||||
      return this.customCronExpression && this.customCronExpression.split(' ').length === 5
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    cronExpressionBlur() {
 | 
					 | 
				
			||||||
      this.customCronError = ''
 | 
					      this.customCronError = ''
 | 
				
			||||||
      if (!this.simpleValidateCustomCron()) {
 | 
					      if (!this.customCronExpression || this.customCronExpression.split(' ').length !== 5) {
 | 
				
			||||||
        this.customCronError = 'Invalid cron expression'
 | 
					        this.customCronError = 'Invalid cron expression'
 | 
				
			||||||
 | 
					        this.isValid = false
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      } else if (this.customCronExpression.split(' ')[0] === '*') {
 | 
				
			||||||
 | 
					        this.customCronError = 'Cannot use * in minutes position'
 | 
				
			||||||
 | 
					        this.isValid = false
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -160,29 +180,65 @@ export default {
 | 
				
			|||||||
        this.selectedMinute = 0
 | 
					        this.selectedMinute = 0
 | 
				
			||||||
        this.cronExpression = this.customCronExpression
 | 
					        this.cronExpression = this.customCronExpression
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.$emit('input', this.cronExpression)
 | 
					
 | 
				
			||||||
 | 
					      if (!this.validatedCron || this.validatedCron !== this.cronExpression) {
 | 
				
			||||||
 | 
					        const validationPayload = await this.validateCron()
 | 
				
			||||||
 | 
					        this.isValid = validationPayload.isValid
 | 
				
			||||||
 | 
					        this.validatedCron = this.cronExpression
 | 
				
			||||||
 | 
					        this.customCronError = validationPayload.error || ''
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.isValid) {
 | 
				
			||||||
 | 
					        this.$emit('input', this.cronExpression)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    validateCron() {
 | 
					    validateCron() {
 | 
				
			||||||
      this.isValidating = true
 | 
					      this.isValidating = true
 | 
				
			||||||
      this.$axios
 | 
					      return this.$axios
 | 
				
			||||||
        .$post('/api/validate-cron', { expression: this.customCronExpression })
 | 
					        .$post('/api/validate-cron', { expression: this.customCronExpression })
 | 
				
			||||||
        .then(() => {
 | 
					        .then(() => {
 | 
				
			||||||
          this.$toast.success('Cron expression is valid!')
 | 
					 | 
				
			||||||
          this.isValidating = false
 | 
					          this.isValidating = false
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            isValid: true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .catch((error) => {
 | 
					        .catch((error) => {
 | 
				
			||||||
          console.error('Invalid cron', error)
 | 
					          console.error('Invalid cron', error)
 | 
				
			||||||
          var errMsg = error.response ? error.response.data || '' : ''
 | 
					          var errMsg = error.response ? error.response.data || '' : ''
 | 
				
			||||||
          this.$toast.error('Invalid cron: ' + errMsg)
 | 
					 | 
				
			||||||
          this.isValidating = false
 | 
					          this.isValidating = false
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            isValid: false,
 | 
				
			||||||
 | 
					            error: errMsg || 'Invalid cron expression'
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    init() {
 | 
					    init() {
 | 
				
			||||||
      if (!this.value) return
 | 
					      if (!this.value) return
 | 
				
			||||||
      // TODO: parse
 | 
					      // TODO: parse
 | 
				
			||||||
      // const pieces = this.value.split(' ')
 | 
					      const pieces = this.value.split(' ')
 | 
				
			||||||
      // this.selectedMinute = Number(pieces[0])
 | 
					      if (pieces.length !== 5) {
 | 
				
			||||||
 | 
					        console.error('Invalid cron expression input', this.value)
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var isCustomCron = false
 | 
				
			||||||
 | 
					      if (isNaN(pieces[0]) || isNaN(pieces[1])) {
 | 
				
			||||||
 | 
					        isCustomCron = true
 | 
				
			||||||
 | 
					      } else if (pieces[2] !== '*' || pieces[3] !== '*') {
 | 
				
			||||||
 | 
					        isCustomCron = true
 | 
				
			||||||
 | 
					      } else if (pieces[4].split(',').some((num) => isNaN(num))) {
 | 
				
			||||||
 | 
					        isCustomCron = true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (isCustomCron) {
 | 
				
			||||||
 | 
					        this.showAdvancedView = true
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.selectedWeekdays = pieces[4].split(',').map((num) => Number(num))
 | 
				
			||||||
 | 
					        this.selectedHour = pieces[1]
 | 
				
			||||||
 | 
					        this.selectedMinute = pieces[0]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.cronExpression = this.value
 | 
				
			||||||
 | 
					      this.customCronExpression = this.value
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mounted() {
 | 
					  mounted() {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user