mirror of https://github.com/openspug/spug
A 发布申请的审核状态已支持消息通知
parent
6a834d446e
commit
d7ad3ee4ff
|
@ -34,6 +34,8 @@ class DeployRequest(models.Model, ModelMixin):
|
||||||
created_by = models.ForeignKey(User, models.PROTECT, related_name='+')
|
created_by = models.ForeignKey(User, models.PROTECT, related_name='+')
|
||||||
approve_at = models.CharField(max_length=20, null=True)
|
approve_at = models.CharField(max_length=20, null=True)
|
||||||
approve_by = models.ForeignKey(User, models.PROTECT, related_name='+', 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):
|
def __repr__(self):
|
||||||
return f'<DeployRequest name={self.name}>'
|
return f'<DeployRequest name={self.name}>'
|
||||||
|
|
|
@ -199,8 +199,98 @@ class Helper:
|
||||||
self.log_key = f'{settings.REQUEST_KEY}:{r_id}'
|
self.log_key = f'{settings.REQUEST_KEY}:{r_id}'
|
||||||
self.rds.delete(self.log_key)
|
self.rds.delete(self.log_key)
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def send_deploy_notify(req):
|
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)
|
rst_notify = json.loads(req.deploy.rst_notify)
|
||||||
host_ids = json.loads(req.host_ids)
|
host_ids = json.loads(req.host_ids)
|
||||||
if rst_notify['mode'] != '0' and rst_notify.get('value'):
|
if rst_notify['mode'] != '0' and rst_notify.get('value'):
|
||||||
|
@ -212,66 +302,34 @@ class Helper:
|
||||||
else:
|
else:
|
||||||
version = extra1
|
version = extra1
|
||||||
else:
|
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)]
|
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])
|
host_str = ', '.join(x['name'] for x in hosts[:2])
|
||||||
if len(hosts) > 2:
|
if len(hosts) > 2:
|
||||||
host_str += f'等{len(hosts)}台主机'
|
host_str += f'等{len(hosts)}台主机'
|
||||||
if rst_notify['mode'] == '1':
|
if rst_notify['mode'] == '1':
|
||||||
color, text = ('#8ece60', '成功') if req.status == '3' else ('#f90202', '失败')
|
data = cls._make_dd_notify(action, req, version, host_str)
|
||||||
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)
|
|
||||||
elif rst_notify['mode'] == '2':
|
elif rst_notify['mode'] == '2':
|
||||||
data = {
|
data = {
|
||||||
|
'action': action,
|
||||||
'req_id': req.id,
|
'req_id': req.id,
|
||||||
'req_name': req.name,
|
'req_name': req.name,
|
||||||
'app_id': req.deploy.app_id,
|
'app_id': req.deploy.app_id,
|
||||||
'app_name': req.deploy.app.name,
|
'app_name': req.deploy.app.name,
|
||||||
'env_id': req.deploy.env_id,
|
'env_id': req.deploy.env_id,
|
||||||
'env_name': req.deploy.env.name,
|
'env_name': req.deploy.env.name,
|
||||||
|
'status': req.status,
|
||||||
|
'reason': req.reason,
|
||||||
'version': version,
|
'version': version,
|
||||||
'targets': hosts,
|
'targets': hosts,
|
||||||
'is_success': req.status == '3',
|
'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':
|
elif rst_notify['mode'] == '3':
|
||||||
color, text = ('info', '成功') if req.status == '3' else ('warning', '失败')
|
data = cls._make_wx_notify(action, req, version, host_str)
|
||||||
texts = [
|
else:
|
||||||
'## %s' % '发布结果通知',
|
raise NotImplementedError
|
||||||
f'**申请标题:** {req.name} ',
|
requests.post(rst_notify['value'], json=data)
|
||||||
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)
|
|
||||||
|
|
||||||
def parse_filter_rule(self, data: str):
|
def parse_filter_rule(self, data: str):
|
||||||
data, files = data.strip(), []
|
data, files = data.strip(), []
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django_redis import get_redis_connection
|
||||||
from libs import json_response, JsonParser, Argument, human_datetime, human_time
|
from libs import json_response, JsonParser, Argument, human_datetime, human_time
|
||||||
from apps.deploy.models import DeployRequest
|
from apps.deploy.models import DeployRequest
|
||||||
from apps.app.models import Deploy
|
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 apps.host.models import Host
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
@ -67,13 +67,18 @@ class RequestView(View):
|
||||||
form.extra = json.dumps(form.extra)
|
form.extra = json.dumps(form.extra)
|
||||||
form.host_ids = json.dumps(form.host_ids)
|
form.host_ids = json.dumps(form.host_ids)
|
||||||
if form.id:
|
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(
|
DeployRequest.objects.filter(pk=form.id).update(
|
||||||
created_by=request.user,
|
created_by=request.user,
|
||||||
reason=None,
|
reason=None,
|
||||||
**form
|
**form
|
||||||
)
|
)
|
||||||
else:
|
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)
|
return json_response(error=error)
|
||||||
|
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
|
@ -182,6 +187,8 @@ class RequestDetailView(View):
|
||||||
outputs = {str(x.id): {'data': []} for x in hosts}
|
outputs = {str(x.id): {'data': []} for x in hosts}
|
||||||
outputs.update(local={'data': [f'{human_time()} 建立接连... ']})
|
outputs.update(local={'data': [f'{human_time()} 建立接连... ']})
|
||||||
req.status = '2'
|
req.status = '2'
|
||||||
|
req.do_at = human_datetime()
|
||||||
|
req.do_by = request.user
|
||||||
if not req.version:
|
if not req.version:
|
||||||
req.version = f'{req.deploy_id}_{req.id}_{datetime.now().strftime("%Y%m%d%H%M%S")}'
|
req.version = f'{req.deploy_id}_{req.id}_{datetime.now().strftime("%Y%m%d%H%M%S")}'
|
||||||
req.save()
|
req.save()
|
||||||
|
@ -206,4 +213,5 @@ class RequestDetailView(View):
|
||||||
req.status = '1' if form.is_pass else '-1'
|
req.status = '1' if form.is_pass else '-1'
|
||||||
req.reason = form.reason
|
req.reason = form.reason
|
||||||
req.save()
|
req.save()
|
||||||
|
Thread(target=Helper.send_deploy_notify, args=(req, 'approve_rst')).start()
|
||||||
return json_response(error=error)
|
return json_response(error=error)
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default observer(function Ext2Setup1() {
|
||||||
checked={info['is_audit']}
|
checked={info['is_audit']}
|
||||||
onChange={v => info['is_audit'] = v}/>
|
onChange={v => info['is_audit'] = v}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="结果通知" help="应用发布成功或失败结果通知">
|
<Form.Item label="消息通知" help="应用审核及发布成功或失败结果通知">
|
||||||
<Input addonBefore={(
|
<Input addonBefore={(
|
||||||
<Select
|
<Select
|
||||||
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>
|
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default observer(function Ext2Setup1() {
|
||||||
checked={info['is_audit']}
|
checked={info['is_audit']}
|
||||||
onChange={v => info['is_audit'] = v}/>
|
onChange={v => info['is_audit'] = v}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="结果通知" help="应用发布成功或失败结果通知">
|
<Form.Item label="消息通知" help="应用审核及发布成功或失败结果通知">
|
||||||
<Input addonBefore={(
|
<Input addonBefore={(
|
||||||
<Select
|
<Select
|
||||||
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>
|
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>
|
||||||
|
|
|
@ -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)
|
|
Loading…
Reference in New Issue