mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-24 19:11:44 +01:00
312 lines
11 KiB
Vue
312 lines
11 KiB
Vue
<template>
|
|
<div class="w-full py-2">
|
|
<div class="flex -mb-px">
|
|
<div class="w-1/2 h-8 rounded-tl-md relative border border-black-200 flex items-center justify-center cursor-pointer" :class="!showAdvancedView ? 'text-white bg-bg hover:bg-opacity-60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary bg-opacity-70 hover:bg-opacity-60'" @click="showAdvancedView = false">
|
|
<p class="text-sm">Scheduler</p>
|
|
</div>
|
|
<div class="w-1/2 h-8 rounded-tr-md relative border border-black-200 flex items-center justify-center -ml-px cursor-pointer" :class="showAdvancedView ? 'text-white bg-bg hover:bg-opacity-60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary bg-opacity-70 hover:bg-opacity-60'" @click="showAdvancedView = true">
|
|
<p class="text-sm">Advanced</p>
|
|
</div>
|
|
</div>
|
|
<div class="px-2 py-4 md:p-4 border border-black-200 rounded-b-md mr-px" style="min-height: 280px">
|
|
<template v-if="!showAdvancedView">
|
|
<ui-dropdown v-model="selectedInterval" @input="updateCron" label="Interval" :items="intervalOptions" class="mb-2" />
|
|
|
|
<ui-multi-select-dropdown v-if="selectedInterval === 'custom'" v-model="selectedWeekdays" @input="updateCron" label="Weekdays to run" :items="weekdays" />
|
|
|
|
<div v-if="(selectedWeekdays.length && selectedInterval === 'custom') || selectedInterval === 'daily'" 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 v-if="description" class="w-full bg-primary bg-opacity-75 rounded-xl p-2 md:p-4 text-center mt-2">
|
|
<p class="text-base md: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 ref="customExpressionInput" v-model="customCronExpression" @blur="cronExpressionBlur" label="Cron Expression" :padding-y="2" text-center class="w-full text-2xl md: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 text-xl" :class="isValid ? 'text-success' : 'text-error'">{{ isValid ? 'check_circle_outline' : 'error_outline' }}</span>
|
|
<p v-if="isValidating" class="text-gray-300 text-base md:text-lg text-center">Checking cron...</p>
|
|
<p v-else-if="customCronError" class="text-error text-base md:text-lg text-center">{{ customCronError }}</p>
|
|
<p v-else class="text-success text-base md:text-lg text-center">Valid cron expression</p>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: {
|
|
value: {
|
|
type: String,
|
|
default: null
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
selectedInterval: 'custom',
|
|
showAdvancedView: false,
|
|
selectedHour: 0,
|
|
selectedMinute: 0,
|
|
selectedWeekdays: [],
|
|
cronExpression: '0 0 * * *',
|
|
customCronExpression: '0 0 * * *',
|
|
customCronError: '',
|
|
isValidating: false,
|
|
validatedCron: null,
|
|
isValid: true
|
|
}
|
|
},
|
|
computed: {
|
|
minuteIsValid() {
|
|
return !(isNaN(this.selectedMinute) || this.selectedMinute === '' || this.selectedMinute < 0 || this.selectedMinute > 59)
|
|
},
|
|
hourIsValid() {
|
|
return !(isNaN(this.selectedHour) || this.selectedHour === '' || this.selectedHour < 0 || this.selectedHour > 23)
|
|
},
|
|
description() {
|
|
if ((this.selectedInterval !== 'custom' || !this.selectedWeekdays.length) && this.selectedInterval !== 'daily') return ''
|
|
|
|
if (!this.hourIsValid) {
|
|
return `<span class="text-error">Invalid hour must be 0-23 | ${this.selectedHour < 0 || this.selectedHour > 23}</span>`
|
|
}
|
|
if (!this.minuteIsValid) {
|
|
return `<span class="text-error">Invalid minute must be 0-59</span>`
|
|
}
|
|
|
|
var description = 'Run every '
|
|
var weekdayTexts = ''
|
|
if (this.selectedWeekdays.length === 7 || this.selectedInterval === 'daily') {
|
|
weekdayTexts = 'day'
|
|
} else {
|
|
weekdayTexts = this.selectedWeekdays
|
|
.map((weekday) => {
|
|
return this.weekdays.find((w) => w.value === weekday).text
|
|
})
|
|
.join(', ')
|
|
}
|
|
description += `<span class="font-bold text-white">${weekdayTexts}</span>`
|
|
|
|
const hourString = this.selectedHour.toString()
|
|
const minuteString = this.selectedMinute.toString().padStart(2, '0')
|
|
description += ` at <span class="font-bold text-white">${hourString}:${minuteString}</span>`
|
|
return description
|
|
},
|
|
intervalOptions() {
|
|
return [
|
|
{
|
|
text: 'Custom daily/weekly',
|
|
value: 'custom'
|
|
},
|
|
{
|
|
text: 'Every day',
|
|
value: 'daily'
|
|
},
|
|
{
|
|
text: 'Every 12 hours',
|
|
value: '0 */12 * * *'
|
|
},
|
|
{
|
|
text: 'Every 6 hours',
|
|
value: '0 */6 * * *'
|
|
},
|
|
{
|
|
text: 'Every 2 hours',
|
|
value: '0 */2 * * *'
|
|
},
|
|
{
|
|
text: 'Every hour',
|
|
value: '0 * * * *'
|
|
},
|
|
{
|
|
text: 'Every 30 minutes',
|
|
value: '*/30 * * * *'
|
|
},
|
|
{
|
|
text: 'Every 15 minutes',
|
|
value: '*/15 * * * *'
|
|
}
|
|
]
|
|
},
|
|
weekdays() {
|
|
return [
|
|
{
|
|
text: 'Sunday',
|
|
value: 0
|
|
},
|
|
{
|
|
text: 'Monday',
|
|
value: 1
|
|
},
|
|
{
|
|
text: 'Tuesday',
|
|
value: 2
|
|
},
|
|
{
|
|
text: 'Wednesday',
|
|
value: 3
|
|
},
|
|
{
|
|
text: 'Thursday',
|
|
value: 4
|
|
},
|
|
{
|
|
text: 'Friday',
|
|
value: 5
|
|
},
|
|
{
|
|
text: 'Saturday',
|
|
value: 6
|
|
}
|
|
]
|
|
}
|
|
},
|
|
methods: {
|
|
checkBlurExpressionInput() {
|
|
if (!this.showAdvancedView || !this.$refs.customExpressionInput) return false
|
|
if (this.$refs.customExpressionInput.isFocused) {
|
|
this.$refs.customExpressionInput.blur()
|
|
return true
|
|
}
|
|
return false
|
|
},
|
|
updateCron() {
|
|
if (this.selectedInterval === 'custom') {
|
|
if (!this.minuteIsValid || !this.hourIsValid || !this.selectedWeekdays.length) {
|
|
this.cronExpression = null
|
|
return
|
|
}
|
|
this.selectedWeekdays.sort()
|
|
|
|
const daysOfWeekPiece = this.selectedWeekdays.length === 7 ? '*' : this.selectedWeekdays.join(',')
|
|
this.cronExpression = `${this.selectedMinute} ${this.selectedHour} * * ${daysOfWeekPiece}`
|
|
} else if (this.selectedInterval === 'daily') {
|
|
if (!this.minuteIsValid || !this.hourIsValid) {
|
|
this.cronExpression = null
|
|
return
|
|
}
|
|
this.cronExpression = `${this.selectedMinute} ${this.selectedHour} * * *`
|
|
} else {
|
|
this.cronExpression = this.selectedInterval
|
|
}
|
|
|
|
this.customCronExpression = this.cronExpression
|
|
this.validatedCron = this.cronExpression
|
|
this.isValid = true
|
|
this.customCronError = ''
|
|
this.$emit('input', this.cronExpression)
|
|
},
|
|
minuteBlur() {
|
|
const v = this.selectedMinute
|
|
if (v === '' || v === null || isNaN(v) || v < 0) {
|
|
this.selectedMinute = 0
|
|
} else if (v > 59) {
|
|
this.selectedMinute = 59
|
|
} else {
|
|
this.selectedMinute = Number(v)
|
|
}
|
|
this.updateCron()
|
|
},
|
|
hourBlur() {
|
|
const v = this.selectedHour
|
|
if (v === '' || v === null || isNaN(v) || v < 0) {
|
|
this.selectedHour = 0
|
|
} else if (v > 23) {
|
|
this.selectedHour = 23
|
|
} else {
|
|
this.selectedHour = Number(v)
|
|
}
|
|
this.updateCron()
|
|
},
|
|
async cronExpressionBlur() {
|
|
this.customCronError = ''
|
|
if (!this.customCronExpression || this.customCronExpression.split(' ').length !== 5) {
|
|
this.customCronError = 'Invalid cron expression'
|
|
this.isValid = false
|
|
return
|
|
}
|
|
|
|
if (this.customCronExpression !== this.cronExpression) {
|
|
this.selectedWeekdays = []
|
|
this.selectedHour = 0
|
|
this.selectedMinute = 0
|
|
this.cronExpression = this.customCronExpression
|
|
}
|
|
|
|
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() {
|
|
this.isValidating = true
|
|
return this.$axios
|
|
.$post('/api/validate-cron', { expression: this.customCronExpression })
|
|
.then(() => {
|
|
this.isValidating = false
|
|
return {
|
|
isValid: true
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error('Invalid cron', error)
|
|
var errMsg = error.response ? error.response.data || '' : ''
|
|
this.isValidating = false
|
|
return {
|
|
isValid: false,
|
|
error: errMsg || 'Invalid cron expression'
|
|
}
|
|
})
|
|
},
|
|
init() {
|
|
if (!this.value) return
|
|
const pieces = this.value.split(' ')
|
|
if (pieces.length !== 5) {
|
|
console.error('Invalid cron expression input', this.value)
|
|
return
|
|
}
|
|
|
|
const intervalMatch = this.intervalOptions.find((opt) => opt.value === this.value)
|
|
if (intervalMatch) {
|
|
this.selectedInterval = this.value
|
|
} else {
|
|
var isCustomCron = false
|
|
if (isNaN(pieces[0]) || isNaN(pieces[1])) {
|
|
isCustomCron = true
|
|
} else if (pieces[2] !== '*' || pieces[3] !== '*') {
|
|
isCustomCron = true
|
|
} else if (pieces[4] !== '*' && pieces[4].split(',').some((num) => isNaN(num))) {
|
|
isCustomCron = true
|
|
}
|
|
|
|
if (isCustomCron) {
|
|
this.showAdvancedView = true
|
|
} else {
|
|
if (pieces[4] === '*') this.selectedInterval = 'daily'
|
|
|
|
this.selectedWeekdays = pieces[4] === '*' ? [0, 1, 2, 3, 4, 5, 6] : pieces[4].split(',').map((num) => Number(num))
|
|
this.selectedHour = pieces[1]
|
|
this.selectedMinute = pieces[0]
|
|
}
|
|
}
|
|
this.cronExpression = this.value
|
|
this.customCronExpression = this.value
|
|
}
|
|
},
|
|
mounted() {
|
|
this.init()
|
|
}
|
|
}
|
|
</script> |