diff --git a/package.json b/package.json index 180afd5..caf1f10 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,9 @@ "dagre-d3": "^0.6.4", "element-ui": "^2.13.2", "flyio": "^0.6.14", + "cron-parser": "^2.10.0", + "debounce": "^1.2.0", + "node-gyp": "^8.3.0", "power-workflow": "^0.0.11", "vue": "^2.6.11", "vue-axios": "^2.1.5", diff --git a/src/components/easy-cron/format-date.js b/src/components/easy-cron/format-date.js new file mode 100644 index 0000000..8a55974 --- /dev/null +++ b/src/components/easy-cron/format-date.js @@ -0,0 +1,37 @@ +const dateFormat = (date, block) => { + if (!date) { + return '' + } + + let format = block || 'yyyy-MM-dd' + + date = new Date(date) + + const map = { + M: date.getMonth() + 1, // 月份 + d: date.getDate(), // 日 + h: date.getHours(), // 小时 + m: date.getMinutes(), // 分 + s: date.getSeconds(), // 秒 + q: Math.floor((date.getMonth() + 3) / 3), // 季度 + S: date.getMilliseconds() // 毫秒 + } + + format = format.replace(/([yMdhmsqS])+/g, (all, t) => { + let v = map[t] + if (v !== undefined) { + if (all.length > 1) { + v = `0${v}` + v = v.substr(v.length - 2) + } + return v + } else if (t === 'y') { + return (date.getFullYear().toString()).substr(4 - all.length) + } + return all + }) + + return format +} + +export default dateFormat diff --git a/src/components/easy-cron/index.vue b/src/components/easy-cron/index.vue new file mode 100644 index 0000000..1f36249 --- /dev/null +++ b/src/components/easy-cron/index.vue @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + 执行时间 + + + + + 执行预览 + + + + + + + + + + + + diff --git a/src/components/easy-cron/input-cron.vue b/src/components/easy-cron/input-cron.vue new file mode 100644 index 0000000..2372cc4 --- /dev/null +++ b/src/components/easy-cron/input-cron.vue @@ -0,0 +1,122 @@ + + + + + 配置cron + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/easy-cron/tabs/const.js b/src/components/easy-cron/tabs/const.js new file mode 100644 index 0000000..e076ee1 --- /dev/null +++ b/src/components/easy-cron/tabs/const.js @@ -0,0 +1,21 @@ +export const WEEK_MAP_EN = { + 'SUN': '0', + 'MON': '1', + 'TUE': '2', + 'WED': '3', + 'THU': '4', + 'FRI': '5', + 'SAT': '6' +} + +export const replaceWeekName = (c) => { + // console.info('after: ' + c) + if (c) { + Object.keys(WEEK_MAP_EN).forEach(k => { + c = c.replace(new RegExp(k, 'g'), WEEK_MAP_EN[k]) + }) + c = c.replace(new RegExp('7', 'g'), '0') + } + // console.info('after: ' + c) + return c +} diff --git a/src/components/easy-cron/tabs/day.vue b/src/components/easy-cron/tabs/day.vue new file mode 100644 index 0000000..dd45b70 --- /dev/null +++ b/src/components/easy-cron/tabs/day.vue @@ -0,0 +1,138 @@ + + + + + 不设置 + 日和周只能设置其中之一 + + + 每日 + + + 区间 + 从日 + 至日 + + + 循环 + 从日开始,间隔 + 日 + + + 工作日 + 本月日,最近的工作日 + + + 最后一日 + + + 指定 + + + + + + + + + + + + + diff --git a/src/components/easy-cron/tabs/hour.vue b/src/components/easy-cron/tabs/hour.vue new file mode 100644 index 0000000..9b6304e --- /dev/null +++ b/src/components/easy-cron/tabs/hour.vue @@ -0,0 +1,101 @@ + + + + + 每时 + + + 区间 + 从时 + 至时 + + + 循环 + 从时开始,间隔 + 时 + + + 指定 + + + + + + + + + + + + + diff --git a/src/components/easy-cron/tabs/minute.vue b/src/components/easy-cron/tabs/minute.vue new file mode 100644 index 0000000..dd83e95 --- /dev/null +++ b/src/components/easy-cron/tabs/minute.vue @@ -0,0 +1,94 @@ + + + + + 每分 + + + 区间 + 从分 + 至分 + + + 循环 + 从 + 分开始,间隔 + 分 + + + 指定 + + + + + + + + + + + + + diff --git a/src/components/easy-cron/tabs/mixin.js b/src/components/easy-cron/tabs/mixin.js new file mode 100644 index 0000000..47504bb --- /dev/null +++ b/src/components/easy-cron/tabs/mixin.js @@ -0,0 +1,151 @@ +// 主要用于日和星期的互斥使用 +const TYPE_NOT_SET = 'TYPE_NOT_SET' +const TYPE_EVERY = 'TYPE_EVERY' +const TYPE_RANGE = 'TYPE_RANGE' +const TYPE_LOOP = 'TYPE_LOOP' +const TYPE_WORK = 'TYPE_WORK' +const TYPE_LAST = 'TYPE_LAST' +const TYPE_SPECIFY = 'TYPE_SPECIFY' + +const DEFAULT_VALUE = '?' + +export default { + model: { + prop: 'prop', + event: 'change' + }, + props: { + prop: { + type: String, + default: DEFAULT_VALUE + }, + disabled: { + type: Boolean, + default: false + } + }, + data () { + const type = TYPE_EVERY + return { + DEFAULT_VALUE, + // 类型 + type, + // 启用日或者星期互斥用 + TYPE_NOT_SET, + TYPE_EVERY, + TYPE_RANGE, + TYPE_LOOP, + TYPE_WORK, + TYPE_LAST, + TYPE_SPECIFY, + // 对于不同的类型,所定义的值也有所不同 + valueRange: { + start: 0, + end: 0 + }, + valueLoop: { + start: 0, + interval: 1 + }, + valueWeek: { + start: 0, + end: 0 + }, + valueList: [], + valueWork: 1, + maxValue: 0, + minValue: 0 + } + }, + watch: { + prop (newVal) { + if (newVal === this.value_c) { + // console.info('skip ' + newVal) + return + } + this.parseProp(newVal) + } + }, + computed: { + value_c () { + let result = [] + switch (this.type) { + case TYPE_NOT_SET: + result.push('?') + break + case TYPE_EVERY: + result.push('*') + break + case TYPE_RANGE: + result.push(`${this.valueRange.start}-${this.valueRange.end}`) + break + case TYPE_LOOP: + result.push(`${this.valueLoop.start}/${this.valueLoop.interval}`) + break + case TYPE_WORK: + result.push(`${this.valueWork}W`) + break + case TYPE_LAST: + result.push('L') + break + case TYPE_SPECIFY: + result.push(this.valueList.join(',')) + break + default: + result.push(this.DEFAULT_VALUE) + break + } + return result.length > 0 ? result.join('') : this.DEFAULT_VALUE + } + }, + methods: { + parseProp (value) { + if (value === this.value_c) { + // console.info('same ' + value) + return + } + if (typeof (this.preProcessProp) === 'function') { + value = this.preProcessProp(value) + } + try { + if (!value || value === this.DEFAULT_VALUE) { + this.type = TYPE_EVERY + } else if (value.indexOf('?') >= 0) { + this.type = TYPE_NOT_SET + } else if (value.indexOf('-') >= 0) { + this.type = TYPE_RANGE + const values = value.split('-') + if (values.length >= 2) { + this.valueRange.start = parseInt(values[0]) + this.valueRange.end = parseInt(values[1]) + } + } else if (value.indexOf('/') >= 0) { + this.type = TYPE_LOOP + const values = value.split('/') + if (values.length >= 2) { + this.valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0]) + this.valueLoop.interval = parseInt(values[1]) + } + } else if (value.indexOf('W') >= 0) { + this.type = TYPE_WORK + const values = value.split('W') + if (!values[0] && !isNaN(values[0])) { + this.valueWork = parseInt(values[0]) + } + } else if (value.indexOf('L') >= 0) { + this.type = TYPE_LAST + const values = value.split('L') + this.valueLast = parseInt(values[0]) + } else if (value.indexOf(',') >= 0 || !isNaN(value)) { + this.type = TYPE_SPECIFY + this.valueList = value.split(',').map(item => parseInt(item)) + } else { + this.type = TYPE_EVERY + } + } catch (e) { + // console.info(e) + this.type = TYPE_EVERY + } + } + } +} diff --git a/src/components/easy-cron/tabs/month.vue b/src/components/easy-cron/tabs/month.vue new file mode 100644 index 0000000..56abbb3 --- /dev/null +++ b/src/components/easy-cron/tabs/month.vue @@ -0,0 +1,101 @@ + + + + + 每月 + + + 区间 + 从月 + 至月 + + + 循环 + 从月开始,间隔 + 月 + + + 指定 + + + + + + + + + + + + + diff --git a/src/components/easy-cron/tabs/second.vue b/src/components/easy-cron/tabs/second.vue new file mode 100644 index 0000000..9580c9f --- /dev/null +++ b/src/components/easy-cron/tabs/second.vue @@ -0,0 +1,103 @@ + + + + + 每秒 + + + 区间 + 从秒 + 至秒 + + + 循环 + 从秒开始,间隔 + 秒 + + + 指定 + + + + + + + + + + + + + diff --git a/src/components/easy-cron/tabs/week.vue b/src/components/easy-cron/tabs/week.vue new file mode 100644 index 0000000..d98b146 --- /dev/null +++ b/src/components/easy-cron/tabs/week.vue @@ -0,0 +1,148 @@ + + + + + 不设置 + 日和周只能设置其中之一 + + + 区间 + 从 + {{ k }} + + 至 + {{ k }} + + + + 循环 + 从 + {{ k }} + 开始,间隔 + 天 + + + 指定 + + + {{k}} + + + + + + + + + + diff --git a/src/components/easy-cron/tabs/year.vue b/src/components/easy-cron/tabs/year.vue new file mode 100644 index 0000000..0117f1f --- /dev/null +++ b/src/components/easy-cron/tabs/year.vue @@ -0,0 +1,95 @@ + + + + + 每年 + + + 区间 + 从年 + 至年 + + + 循环 + 从年开始,间隔 + 年 + + + + + + + + diff --git a/src/components/easy-cron/validator.js b/src/components/easy-cron/validator.js new file mode 100644 index 0000000..80d7890 --- /dev/null +++ b/src/components/easy-cron/validator.js @@ -0,0 +1,51 @@ +import CronParser from 'cron-parser' +import { replaceWeekName } from './tabs/const' + +export default (rule, value, callback) => { + // 没填写就不校验 + if (!value) { + callback() + return true + } + const values = value.split(' ').filter(item => !!item) + if (values.length > 7) { + callback(new Error('cron表达式最多7项')) + return false + } + // 检查第7项 + let e = value + if (values.length === 7) { + const year = replaceWeekName(values[6]) + if (year !== '*' && year !== '?') { + let yearValues = [] + if (year.indexOf('-') >= 0) { + yearValues = year.split('-') + } else if (year.indexOf('/')) { + yearValues = year.split('/') + } else { + yearValues = [year] + } + // console.info(yearValues) + // 判断是否都是数字 + const checkYear = yearValues.some(item => isNaN(item)) + if (checkYear) { + callback(new Error('cron表达式参数[年]错误:' + year)) + return false + } + } + // 取其中的前六项 + e = values.slice(0, 6).join(' ') + } + // 6位 没有年 + // 5位没有秒、年 + let result = true + try { + const iter = CronParser.parseExpression(e) + iter.next() + callback() + } catch (e) { + callback(new Error('cron表达式错误:' + e)) + result = false + } + return result +} diff --git a/src/components/views/JobManager.vue b/src/components/views/JobManager.vue index db49a9d..36b9ca9 100644 --- a/src/components/views/JobManager.vue +++ b/src/components/views/JobManager.vue @@ -1,441 +1,518 @@ - + - - + + - - - - - - - - - - - {{$t('message.query')}} - {{$t('message.reset')}} - - - + + + + + + + + + + + {{ $t('message.query') }} + {{ $t('message.reset') }} + + + - - - - {{$t('message.newJob')}} - - - + + + + {{ $t('message.newJob') }} + + + - - - - - - - - {{scope.row.timeExpressionType}} {{scope.row.timeExpression}} - - - - - {{translateExecuteType(scope.row.executeType)}} - - - - - {{translateProcessorType(scope.row.processorType)}} - - - - - - - - - - {{$t('message.edit')}} - {{$t('message.run')}} - - {{$t('message.more')}} - - - {{$t('message.runHistory')}} - - - {{$t('message.copy')}} - - - {{$t('message.delete')}} - - - - - - - + + + + + + + + {{ scope.row.timeExpressionType }} {{ scope.row.timeExpression }} + + + + + {{ translateExecuteType(scope.row.executeType) }} + + + + + {{ translateProcessorType(scope.row.processorType) }} + + + + + + + + + + {{ $t('message.edit') }} + {{ $t('message.run') }} + + {{ $t('message.more') }} + + + + {{ $t('message.runHistory') }} + + + + {{ $t('message.copy') }} + + + + {{ $t('message.delete') }} + + + + + + + + - - - - + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - {{$t('message.validateTimeExpression')}} - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - {{$t('message.maxInstanceNum')}} - - - - - {{$t('message.threadConcurrency')}} - - - - - {{$t('message.timeout')}} - - - - - - - - - {{$t('message.taskRetryTimes')}} - - - - - {{$t('message.subTaskRetryTimes')}} - - - - - - - - - {{$t('message.minCPU')}} - - - - - {{$t('message.minMemory')}} - - - - - {{$t('message.minDisk')}} - - - - - - - - - {{$t('message.designatedWorkerAddress')}} - - - - - {{$t('message.maxWorkerNum')}} - - - - - - - - - - + + + + + + + + + + {{ $t('message.maxInstanceNum') }} + + + + + {{ $t('message.threadConcurrency') }} + + + + + {{ $t('message.timeout') }} + + + + + + + + + {{ $t('message.taskRetryTimes') }} + + + + + {{ $t('message.subTaskRetryTimes') }} + + + + + + + + + {{ $t('message.minCPU') }} + + + + + {{ $t('message.minMemory') }} + + + + + {{ $t('message.minDisk') }} + + + + + + + + + {{ $t('message.designatedWorkerAddress') }} + + + + + {{ $t('message.maxWorkerNum') }} + + + + + + + + + + + + + + + + - - {{$t('message.save')}} - {{$t('message.cancel')}} - + + {{ $t('message.save') }} + {{ $t('message.cancel') }} + - - + + - - - - + + + +