diff --git a/spug_api/apps/schedule/urls.py b/spug_api/apps/schedule/urls.py index 8abdb36..fc26420 100644 --- a/spug_api/apps/schedule/urls.py +++ b/spug_api/apps/schedule/urls.py @@ -8,4 +8,5 @@ from .views import * urlpatterns = [ path('', Schedule.as_view()), path('/', HistoryView.as_view()), + path('run_time/', next_run_time), ] diff --git a/spug_api/apps/schedule/views.py b/spug_api/apps/schedule/views.py index 9a888c6..55e0994 100644 --- a/spug_api/apps/schedule/views.py +++ b/spug_api/apps/schedule/views.py @@ -3,7 +3,9 @@ # Released under the MIT License. from django.views.generic import View from django_redis import get_redis_connection +from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger +from apps.schedule.scheduler import Scheduler from apps.schedule.models import Task, History from apps.schedule.executors import dispatch from apps.host.models import Host @@ -135,3 +137,29 @@ class HistoryView(View): 'output': out}) data['duration'] = f"{data['duration'] / len(outputs):.3f}" return data + + +def next_run_time(request): + form, error = JsonParser( + Argument('rule', help='参数错误'), + Argument('start', required=False), + Argument('stop', required=False) + ).parse(request.body) + if error is None: + try: + minute, hour, day, month, week = form.rule.split() + week = Scheduler.week_map[week] + trigger = CronTrigger(minute=minute, hour=hour, day=day, month=month, day_of_week=week, + start_date=form.start, end_date=form.stop) + except (ValueError, KeyError): + return json_response({'success': False, 'msg': '无效的执行规则'}) + scheduler = BackgroundScheduler(timezone=settings.TIME_ZONE) + scheduler.start() + job = scheduler.add_job(lambda: None, trigger) + run_time = job.next_run_time + scheduler.shutdown() + if run_time: + return json_response({'success': True, 'msg': run_time.strftime('%Y-%m-%d %H:%M:%S')}) + else: + return json_response({'success': False, 'msg': '无法被触发'}) + return json_response(error=error) diff --git a/spug_web/src/pages/schedule/Form.js b/spug_web/src/pages/schedule/Form.js index 444d9f3..882d1fa 100644 --- a/spug_web/src/pages/schedule/Form.js +++ b/spug_web/src/pages/schedule/Form.js @@ -20,10 +20,13 @@ class ComForm extends React.Component { constructor(props) { super(props); this.isFirstRender = true; + this.lastFetchId = 0; + this._fetchNextRunTime = lds.debounce(this._fetchNextRunTime, 500); this.state = { loading: false, type: null, page: 0, + nextRunTime: null, args: {[store.record['trigger']]: store.record['trigger_args']}, command: store.record['command'], } @@ -100,7 +103,35 @@ class ComForm extends React.Component { handleCronArgs = (key, value) => { let args = this.state.args['cron'] || {}; args = Object.assign(args, {[key]: value}); - this.setState({args: Object.assign(this.state.args, {cron: args})}) + this.setState({args: Object.assign(this.state.args, {cron: args})}, () => { + if (key === 'rule') { + value = value.trim(); + if (value.split(' ').length === 5) { + this.setState({nextRunTime: }); + this._fetchNextRunTime() + } else { + this.setState({nextRunTime: null}) + } + } else { + this.setState({nextRunTime: }); + this._fetchNextRunTime() + } + }); + }; + + _fetchNextRunTime = () => { + this.lastFetchId += 1; + const fetchId = this.lastFetchId; + const args = this._parse_args('cron'); + http.post('/api/schedule/run_time/', JSON.parse(args)) + .then(({success, msg}) => { + if (fetchId !== this.lastFetchId) return; + if (success) { + this.setState({nextRunTime: {msg}}) + } else { + this.setState({nextRunTime: {msg}}) + } + }) }; verifyButtonStatus = () => { @@ -118,7 +149,7 @@ class ComForm extends React.Component { render() { const info = store.record; const {getFieldDecorator} = this.props.form; - const {page, args, loading, showTmp} = this.state; + const {page, args, loading, showTmp, nextRunTime} = this.state; const [b1, b2, b3] = this.verifyButtonStatus(); return ( } value={lds.get(args, 'cron.rule')} placeholder="例如每天凌晨1点执行:0 1 * * *" onChange={e => this.handleCronArgs('rule', e.target.value)}/>