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='+')
|
||||
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}>'
|
||||
|
|
|
@ -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(), []
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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