mirror of https://github.com/openspug/spug
A 任务计划增加立即执行测试功能
parent
b0879bfa6c
commit
46cb442d50
|
@ -36,7 +36,8 @@ def host_executor(q, host, pkey, command):
|
||||||
q.put((host.id, exit_code, round(time.time() - now, 3), out))
|
q.put((host.id, exit_code, round(time.time() - now, 3), out))
|
||||||
|
|
||||||
|
|
||||||
def dispatch(command, targets):
|
def dispatch(command, targets, in_view=False):
|
||||||
|
if not in_view:
|
||||||
close_old_connections()
|
close_old_connections()
|
||||||
threads, pkey, q = [], AppSetting.get('private_key'), Queue()
|
threads, pkey, q = [], AppSetting.get('private_key'), Queue()
|
||||||
for t in targets:
|
for t in targets:
|
||||||
|
|
|
@ -7,6 +7,5 @@ from .views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', Schedule.as_view()),
|
path('', Schedule.as_view()),
|
||||||
path('<int:h_id>/', ScheduleInfo.as_view()),
|
path('<int:t_id>/', HistoryView.as_view()),
|
||||||
path('<int:t_id>/history/', HistoryView.as_view()),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,7 @@ from django.views.generic import View
|
||||||
from django_redis import get_redis_connection
|
from django_redis import get_redis_connection
|
||||||
from apscheduler.triggers.cron import CronTrigger
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
from apps.schedule.models import Task, History
|
from apps.schedule.models import Task, History
|
||||||
|
from apps.schedule.executors import dispatch
|
||||||
from apps.host.models import Host
|
from apps.host.models import Host
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from libs import json_response, JsonParser, Argument, human_datetime
|
from libs import json_response, JsonParser, Argument, human_datetime
|
||||||
|
@ -90,13 +91,34 @@ class Schedule(View):
|
||||||
|
|
||||||
class HistoryView(View):
|
class HistoryView(View):
|
||||||
def get(self, request, t_id):
|
def get(self, request, t_id):
|
||||||
|
task = Task.objects.filter(pk=t_id).first()
|
||||||
|
if not task:
|
||||||
|
return json_response(error='未找到指定任务')
|
||||||
|
|
||||||
h_id = request.GET.get('id')
|
h_id = request.GET.get('id')
|
||||||
if h_id:
|
if h_id:
|
||||||
return json_response(self.fetch_detail(h_id))
|
h_id = task.latest_id if h_id == 'latest' else h_id
|
||||||
|
return json_response(self._fetch_detail(h_id))
|
||||||
histories = History.objects.filter(task_id=t_id)
|
histories = History.objects.filter(task_id=t_id)
|
||||||
return json_response([x.to_list() for x in histories])
|
return json_response([x.to_list() for x in histories])
|
||||||
|
|
||||||
def fetch_detail(self, h_id):
|
def post(self, request, t_id):
|
||||||
|
task = Task.objects.filter(pk=t_id).first()
|
||||||
|
if not task:
|
||||||
|
return json_response(error='未找到指定任务')
|
||||||
|
data = dispatch(task.command, json.loads(task.targets), True)
|
||||||
|
score = 0
|
||||||
|
for item in data:
|
||||||
|
score += 1 if item[1] else 0
|
||||||
|
history = History.objects.create(
|
||||||
|
task_id=t_id,
|
||||||
|
status=2 if score == len(data) else 1 if score else 0,
|
||||||
|
run_time=human_datetime(),
|
||||||
|
output=json.dumps(data)
|
||||||
|
)
|
||||||
|
return json_response(history.id)
|
||||||
|
|
||||||
|
def _fetch_detail(self, h_id):
|
||||||
record = History.objects.filter(pk=h_id).first()
|
record = History.objects.filter(pk=h_id).first()
|
||||||
outputs = json.loads(record.output)
|
outputs = json.loads(record.output)
|
||||||
host_ids = (x[0] for x in outputs if isinstance(x[0], int))
|
host_ids = (x[0] for x in outputs if isinstance(x[0], int))
|
||||||
|
@ -113,23 +135,3 @@ class HistoryView(View):
|
||||||
'output': out})
|
'output': out})
|
||||||
data['duration'] = f"{data['duration'] / len(outputs):.3f}"
|
data['duration'] = f"{data['duration'] / len(outputs):.3f}"
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ScheduleInfo(View):
|
|
||||||
def get(self, request, h_id):
|
|
||||||
history = History.objects.filter(pk=h_id).first()
|
|
||||||
outputs = json.loads(history.output)
|
|
||||||
host_ids = (x[0] for x in outputs if isinstance(x[0], int))
|
|
||||||
hosts_info = {x.id: x.name for x in Host.objects.filter(id__in=host_ids)}
|
|
||||||
data = {'run_time': history.run_time, 'success': 0, 'failure': 0, 'duration': 0, 'outputs': []}
|
|
||||||
for h_id, code, duration, out in outputs:
|
|
||||||
key = 'success' if code == 0 else 'failure'
|
|
||||||
data[key] += 1
|
|
||||||
data['duration'] += duration
|
|
||||||
data['outputs'].append({
|
|
||||||
'name': hosts_info.get(h_id, '本机'),
|
|
||||||
'code': code,
|
|
||||||
'duration': duration,
|
|
||||||
'output': out})
|
|
||||||
data['duration'] = f"{data['duration'] / len(outputs):.3f}"
|
|
||||||
return json_response(data)
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ class ComForm extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
http.get(`/api/schedule/${store.record.id}/`)
|
http.get(`/api/schedule/${store.record.id}/?id=${store.record.h_id}`)
|
||||||
.then(info => this.setState({info}))
|
.then(info => this.setState({info}))
|
||||||
.finally(() => this.setState({loading: false}))
|
.finally(() => this.setState({loading: false}))
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class ComForm extends React.Component {
|
||||||
tab={item.code === 0 ? item.name : <span style={{color: 'red'}}>{item.name}</span>}>
|
tab={item.code === 0 ? item.name : <span style={{color: 'red'}}>{item.name}</span>}>
|
||||||
<div>执行时间: {run_time}({moment(run_time).fromNow()})</div>
|
<div>执行时间: {run_time}({moment(run_time).fromNow()})</div>
|
||||||
<div style={{marginTop: 5}}>运行耗时: {item.duration} s</div>
|
<div style={{marginTop: 5}}>运行耗时: {item.duration} s</div>
|
||||||
<div style={{marginTop: 5}}>返回状态: {item.code}</div>
|
<div style={{marginTop: 5}}>返回状态: {item.code}(非 0 则判定为失败)</div>
|
||||||
<div style={{marginTop: 5}}>执行输出: <pre style={preStyle}>{item.output}</pre></div>
|
<div style={{marginTop: 5}}>执行输出: <pre style={preStyle}>{item.output}</pre></div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Record extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
http.get(`/api/schedule/${store.record.id}/history/`)
|
http.get(`/api/schedule/${store.record.id}/`)
|
||||||
.then(res => this.setState({records: res}))
|
.then(res => this.setState({records: res}))
|
||||||
.finally(() => this.setState({loading: false}))
|
.finally(() => this.setState({loading: false}))
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class Record extends React.Component {
|
||||||
render: info => <Tag color={this.colors[info['status']]}>{info['status_alias']}</Tag>
|
render: info => <Tag color={this.colors[info['status']]}>{info['status_alias']}</Tag>
|
||||||
}, {
|
}, {
|
||||||
title: '操作',
|
title: '操作',
|
||||||
render: info => <LinkButton onClick={() => store.showInfo(info)}>详情</LinkButton>
|
render: info => <LinkButton onClick={() => store.showInfo(null, info.id)}>详情</LinkButton>
|
||||||
}];
|
}];
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -24,10 +24,13 @@ class ComTable extends React.Component {
|
||||||
moreMenus = (info) => (
|
moreMenus = (info) => (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<LinkButton auth="schedule.schedule.edit" onClick={() => this.handleActive(info)}>{info.is_active ? '禁用' : '激活'}</LinkButton>
|
<LinkButton onClick={() => this.handleTest(info)}>执行测试</LinkButton>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<LinkButton onClick={() => store.showRecord(info)}>历史</LinkButton>
|
<LinkButton auth="schedule.schedule.edit" onClick={() => this.handleActive(info)}>{info.is_active ? '禁用任务' : '激活任务'}</LinkButton>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
<LinkButton onClick={() => store.showRecord(info)}>历史记录</LinkButton>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Divider/>
|
<Menu.Divider/>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
|
@ -72,7 +75,7 @@ class ComTable extends React.Component {
|
||||||
width: 180,
|
width: 180,
|
||||||
render: info => (
|
render: info => (
|
||||||
<span>
|
<span>
|
||||||
<LinkButton disabled={!info['latest_run_time']} onClick={() => store.showInfo(info, true)}>详情</LinkButton>
|
<LinkButton disabled={!info['latest_run_time']} onClick={() => store.showInfo(info)}>详情</LinkButton>
|
||||||
<Divider type="vertical"/>
|
<Divider type="vertical"/>
|
||||||
<LinkButton auth="schedule.schedule.edit" onClick={() => store.showForm(info)}>编辑</LinkButton>
|
<LinkButton auth="schedule.schedule.edit" onClick={() => store.showForm(info)}>编辑</LinkButton>
|
||||||
<Divider type="vertical"/>
|
<Divider type="vertical"/>
|
||||||
|
@ -113,6 +116,15 @@ class ComTable extends React.Component {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleTest = (text) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '操作确认',
|
||||||
|
content: '立即执行该任务(不影响调度规则)?',
|
||||||
|
onOk: () => http.post(`/api/schedule/${text.id}/`)
|
||||||
|
.then(res => store.showInfo(text, res))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let data = store.records;
|
let data = store.records;
|
||||||
if (store.f_status !== undefined) {
|
if (store.f_status !== undefined) {
|
||||||
|
|
|
@ -41,10 +41,10 @@ class Store {
|
||||||
this.record = info
|
this.record = info
|
||||||
};
|
};
|
||||||
|
|
||||||
showInfo = (info = {}, isTask) => {
|
showInfo = (info, h_id='latest') => {
|
||||||
const record = isTask ? {id: info['latest_id']} : info;
|
if (info) this.record = info;
|
||||||
this.infoVisible = true;
|
this.record.h_id = h_id;
|
||||||
this.record = record
|
this.infoVisible = true
|
||||||
};
|
};
|
||||||
|
|
||||||
showRecord = (info) => {
|
showRecord = (info) => {
|
||||||
|
|
Loading…
Reference in New Issue