A 任务计划Cron新增实时显示预估执行时间特性

pull/137/head
vapao 2020-06-13 09:44:06 +08:00
parent 1b0b2cfabd
commit 4daa6f80a2
3 changed files with 63 additions and 2 deletions

View File

@ -8,4 +8,5 @@ from .views import *
urlpatterns = [
path('', Schedule.as_view()),
path('<int:t_id>/', HistoryView.as_view()),
path('run_time/', next_run_time),
]

View File

@ -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)

View File

@ -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: <Icon type="loading"/>});
this._fetchNextRunTime()
} else {
this.setState({nextRunTime: null})
}
} else {
this.setState({nextRunTime: <Icon type="loading"/>});
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: <span style={{fontSize: 12, color: '#52c41a'}}>{msg}</span>})
} else {
this.setState({nextRunTime: <span style={{fontSize: 12, color: '#ff4d4f'}}>{msg}</span>})
}
})
};
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 (
<Modal
@ -230,6 +261,7 @@ class ComForm extends React.Component {
<Tabs.TabPane tab="UNIX Cron" key="cron">
<Form.Item required label="执行规则" help="兼容Cron风格可参考官方例子">
<Input
suffix={nextRunTime || <span/>}
value={lds.get(args, 'cron.rule')}
placeholder="例如每天凌晨1点执行0 1 * * *"
onChange={e => this.handleCronArgs('rule', e.target.value)}/>