A 发布申请的审核状态已支持消息通知

pull/154/head
vapao 2020-07-16 19:56:45 +08:00
parent 6a834d446e
commit d7ad3ee4ff
6 changed files with 116 additions and 157 deletions

View File

@ -34,6 +34,8 @@ class DeployRequest(models.Model, ModelMixin):
created_by = models.ForeignKey(User, models.PROTECT, related_name='+')
approve_at = models.CharField(max_length=20, null=True)
approve_by = models.ForeignKey(User, models.PROTECT, related_name='+', null=True)
do_at = models.CharField(max_length=20, null=True)
do_by = models.ForeignKey(User, models.PROTECT, related_name='+', null=True)
def __repr__(self):
return f'<DeployRequest name={self.name}>'

View File

@ -199,8 +199,98 @@ class Helper:
self.log_key = f'{settings.REQUEST_KEY}:{r_id}'
self.rds.delete(self.log_key)
@staticmethod
def send_deploy_notify(req):
@classmethod
def _make_dd_notify(cls, action, req, version, host_str):
texts = [
f'**申请标题:** {req.name}',
f'**应用名称:** {req.deploy.app.name}',
f'**应用版本:** {version}',
f'**发布环境:** {req.deploy.env.name}',
f'**发布主机:** {host_str}',
]
if action == 'approve_req':
texts.insert(0, '## %s ## ' % '发布审核申请')
texts.extend([
f'**申请人员:** {req.created_by.nickname}',
f'**申请时间:** {human_datetime()}',
'> 来自 Spug运维平台'
])
elif action == 'approve_rst':
color, text = ('#008000', '通过') if req.status == '1' else ('#f90202', '驳回')
texts.insert(0, '## %s ## ' % '发布审核结果')
texts.extend([
f'**审核人员:** {req.approve_by.nickname}',
f'**审核结果:** <font color="{color}">{text}</font>',
f'**审核意见:** {req.reason or ""}',
f'**审核时间:** {human_datetime()}',
'> 来自 Spug运维平台'
])
else:
color, text = ('#008000', '成功') if req.status == '3' else ('#f90202', '失败')
texts.insert(0, '## %s ## ' % '发布结果通知')
if req.approve_at:
texts.append(f'**审核人员:** {req.approve_by.nickname}')
texts.extend([
f'**执行人员:** {req.do_by.nickname}',
f'**发布结果:** <font color="{color}">{text}</font>',
f'**发布时间:** {human_datetime()}',
'> 来自 Spug运维平台'
])
return {
'msgtype': 'markdown',
'markdown': {
'title': 'Spug 发布消息通知',
'text': '\n\n'.join(texts)
}
}
@classmethod
def _make_wx_notify(cls, action, req, version, host_str):
texts = [
f'申请标题: {req.name}',
f'应用名称: {req.deploy.app.name}',
f'应用版本: {version}',
f'发布环境: {req.deploy.env.name}',
f'发布主机: {host_str}',
]
if action == 'approve_req':
texts.insert(0, '## %s' % '发布审核申请')
texts.extend([
f'申请人员: {req.created_by.nickname}',
f'申请时间: {human_datetime()}',
'> 来自 Spug运维平台'
])
elif action == 'approve_rst':
color, text = ('info', '通过') if req.status == '1' else ('warning', '驳回')
texts.insert(0, '## %s' % '发布审核结果')
texts.extend([
f'审核人员: {req.approve_by.nickname}',
f'审核结果: <font color="{color}">{text}</font>',
f'审核意见: {req.reason or ""}',
f'审核时间: {human_datetime()}',
'> 来自 Spug运维平台'
])
else:
color, text = ('info', '成功') if req.status == '3' else ('warning', '失败')
texts.insert(0, '## %s' % '发布结果通知')
if req.approve_at:
texts.append(f'审核人员: {req.approve_by.nickname}')
texts.extend([
f'执行人员: {req.do_by.nickname}',
f'发布结果: <font color="{color}">{text}</font>',
f'发布时间: {human_datetime()}',
'> 来自 Spug运维平台'
])
return {
'msgtype': 'markdown',
'markdown': {
'content': '\n'.join(texts)
}
}
@classmethod
def send_deploy_notify(cls, req, action=None):
rst_notify = json.loads(req.deploy.rst_notify)
host_ids = json.loads(req.host_ids)
if rst_notify['mode'] != '0' and rst_notify.get('value'):
@ -212,66 +302,34 @@ class Helper:
else:
version = extra1
else:
version = extra[0]
version = extra[0] or ''
hosts = [{'id': x.id, 'name': x.name} for x in Host.objects.filter(id__in=host_ids)]
host_str = ', '.join(x['name'] for x in hosts[:2])
if len(hosts) > 2:
host_str += f'{len(hosts)}台主机'
if rst_notify['mode'] == '1':
color, text = ('#8ece60', '成功') if req.status == '3' else ('#f90202', '失败')
texts = [
'## %s ## ' % '发布结果通知',
f'**申请标题:** {req.name} ',
f'**应用名称:** {req.deploy.app.name} ',
f'**应用版本:** {version} ',
f'**发布环境:** {req.deploy.env.name} ',
f'**发布主机:** {host_str} ',
f'**发布结果:** <font color="{color}">{text}</font>',
f'**发布时间:** {human_datetime()} ',
'> 来自 Spug运维平台'
]
data = {
'msgtype': 'markdown',
'markdown': {
'title': '发布结果通知',
'text': '\n\n'.join(texts)
}
}
requests.post(rst_notify['value'], json=data)
data = cls._make_dd_notify(action, req, version, host_str)
elif rst_notify['mode'] == '2':
data = {
'action': action,
'req_id': req.id,
'req_name': req.name,
'app_id': req.deploy.app_id,
'app_name': req.deploy.app.name,
'env_id': req.deploy.env_id,
'env_name': req.deploy.env.name,
'status': req.status,
'reason': req.reason,
'version': version,
'targets': hosts,
'is_success': req.status == '3',
'deploy_at': human_datetime()
'created_at': human_datetime()
}
requests.post(rst_notify['value'], json=data)
elif rst_notify['mode'] == '3':
color, text = ('info', '成功') if req.status == '3' else ('warning', '失败')
texts = [
'## %s' % '发布结果通知',
f'**申请标题:** {req.name} ',
f'**应用名称:** {req.deploy.app.name} ',
f'**应用版本:** {version} ',
f'**发布环境:** {req.deploy.env.name} ',
f'**发布主机:** {host_str} ',
f'**发布结果:** <font color="{color}">{text}</font>',
f'**发布时间:** {human_datetime()} ',
'> 来自 Spug运维平台'
]
data = {
'msgtype': 'markdown',
'markdown': {
'content': '\n'.join(texts)
}
}
requests.post(rst_notify['value'], json=data)
data = cls._make_wx_notify(action, req, version, host_str)
else:
raise NotImplementedError
requests.post(rst_notify['value'], json=data)
def parse_filter_rule(self, data: str):
data, files = data.strip(), []

View File

@ -8,7 +8,7 @@ from django_redis import get_redis_connection
from libs import json_response, JsonParser, Argument, human_datetime, human_time
from apps.deploy.models import DeployRequest
from apps.app.models import Deploy
from apps.deploy.utils import deploy_dispatch
from apps.deploy.utils import deploy_dispatch, Helper
from apps.host.models import Host
from collections import defaultdict
from threading import Thread
@ -67,13 +67,18 @@ class RequestView(View):
form.extra = json.dumps(form.extra)
form.host_ids = json.dumps(form.host_ids)
if form.id:
req = DeployRequest.objects.get(pk=form.id)
is_required_notify = deploy.is_audit and req.status == '-1'
DeployRequest.objects.filter(pk=form.id).update(
created_by=request.user,
reason=None,
**form
)
else:
DeployRequest.objects.create(created_by=request.user, **form)
req = DeployRequest.objects.create(created_by=request.user, **form)
is_required_notify = deploy.is_audit
if is_required_notify:
Thread(target=Helper.send_deploy_notify, args=(req, 'approve_req')).start()
return json_response(error=error)
def put(self, request):
@ -182,6 +187,8 @@ class RequestDetailView(View):
outputs = {str(x.id): {'data': []} for x in hosts}
outputs.update(local={'data': [f'{human_time()} 建立接连... ']})
req.status = '2'
req.do_at = human_datetime()
req.do_by = request.user
if not req.version:
req.version = f'{req.deploy_id}_{req.id}_{datetime.now().strftime("%Y%m%d%H%M%S")}'
req.save()
@ -206,4 +213,5 @@ class RequestDetailView(View):
req.status = '1' if form.is_pass else '-1'
req.reason = form.reason
req.save()
Thread(target=Helper.send_deploy_notify, args=(req, 'approve_rst')).start()
return json_response(error=error)

View File

@ -36,7 +36,7 @@ export default observer(function Ext2Setup1() {
checked={info['is_audit']}
onChange={v => info['is_audit'] = v}/>
</Form.Item>
<Form.Item label="结果通知" help="应用发布成功或失败结果通知">
<Form.Item label="消息通知" help="应用审核及发布成功或失败结果通知">
<Input addonBefore={(
<Select
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>

View File

@ -33,7 +33,7 @@ export default observer(function Ext2Setup1() {
checked={info['is_audit']}
onChange={v => info['is_audit'] = v}/>
</Form.Item>
<Form.Item label="结果通知" help="应用发布成功或失败结果通知">
<Form.Item label="消息通知" help="应用审核及发布成功或失败结果通知">
<Input addonBefore={(
<Select
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>

View File

@ -1,109 +0,0 @@
/**
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
* Copyright (c) <spug.dev@gmail.com>
* Released under the AGPL-3.0 License.
*/
import React from 'react';
import { observer } from 'mobx-react';
import { Modal, Form, Input, Select, Col, Button, message } from 'antd';
import { ACEditor } from 'components';
import http from 'libs/http';
import store from './store';
@observer
class ComForm extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
type: null,
body: store.record['body'],
}
}
handleSubmit = () => {
this.setState({loading: true});
const formData = this.props.form.getFieldsValue();
formData['id'] = store.record.id;
formData['body'] = this.state.body;
http.post('/api/exec/template/', formData)
.then(res => {
message.success('操作成功');
store.formVisible = false;
store.fetchRecords()
}, () => this.setState({loading: false}))
};
handleAddZone = () => {
Modal.confirm({
icon: 'exclamation-circle',
title: '添加模板类型',
content: this.addZoneForm,
onOk: () => {
if (this.state.type) {
store.types.push(this.state.type);
this.props.form.setFieldsValue({'type': this.state.type})
}
},
})
};
addZoneForm = (
<Form>
<Form.Item required label="模板类型">
<Input onChange={val => this.setState({type: val.target.value})}/>
</Form.Item>
</Form>
);
render() {
const info = store.record;
const {getFieldDecorator} = this.props.form;
return (
<Modal
visible
width={800}
maskClosable={false}
title={store.record.id ? '编辑模板' : '新建模板'}
onCancel={() => store.formVisible = false}
confirmLoading={this.state.loading}
onOk={this.handleSubmit}>
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
<Form.Item required label="模板类型">
<Col span={16}>
{getFieldDecorator('type', {initialValue: info['type']})(
<Select placeholder="请选择模板类型">
{store.types.map(item => (
<Select.Option value={item} key={item}>{item}</Select.Option>
))}
</Select>
)}
</Col>
<Col span={6} offset={2}>
<Button type="link" onClick={this.handleAddZone}>添加类型</Button>
</Col>
</Form.Item>
<Form.Item required label="模板名称">
{getFieldDecorator('name', {initialValue: info['name']})(
<Input placeholder="请输入模板名称"/>
)}
</Form.Item>
<Form.Item required label="模板内容">
<ACEditor
mode="sh"
value={this.state.body}
onChange={val => this.setState({body: val})}
height="300px"/>
</Form.Item>
<Form.Item label="备注信息">
{getFieldDecorator('desc', {initialValue: info['desc']})(
<Input.TextArea placeholder="请输入模板备注信息"/>
)}
</Form.Item>
</Form>
</Modal>
)
}
}
export default Form.create()(ComForm)