diff --git a/spug_api/apps/deploy/models.py b/spug_api/apps/deploy/models.py index 2d7d7e5..2f639a7 100644 --- a/spug_api/apps/deploy/models.py +++ b/spug_api/apps/deploy/models.py @@ -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'' diff --git a/spug_api/apps/deploy/utils.py b/spug_api/apps/deploy/utils.py index 7ef4dcc..7538652 100644 --- a/spug_api/apps/deploy/utils.py +++ b/spug_api/apps/deploy/utils.py @@ -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'**审核结果:** {text}', + 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'**发布结果:** {text}', + 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'审核结果: {text}', + 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'发布结果: {text}', + 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'**发布结果:** {text}', - 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'**发布结果:** {text}', - 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(), [] diff --git a/spug_api/apps/deploy/views.py b/spug_api/apps/deploy/views.py index 61f2490..6b3abca 100644 --- a/spug_api/apps/deploy/views.py +++ b/spug_api/apps/deploy/views.py @@ -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) diff --git a/spug_web/src/pages/deploy/app/Ext1Setup1.js b/spug_web/src/pages/deploy/app/Ext1Setup1.js index 5714116..a754e7a 100644 --- a/spug_web/src/pages/deploy/app/Ext1Setup1.js +++ b/spug_web/src/pages/deploy/app/Ext1Setup1.js @@ -36,7 +36,7 @@ export default observer(function Ext2Setup1() { checked={info['is_audit']} onChange={v => info['is_audit'] = v}/> - + info['rst_notify']['mode'] = v}> diff --git a/spug_web/src/pages/deploy/app/Ext2Setup1.js b/spug_web/src/pages/deploy/app/Ext2Setup1.js index dcf5bee..327d008 100644 --- a/spug_web/src/pages/deploy/app/Ext2Setup1.js +++ b/spug_web/src/pages/deploy/app/Ext2Setup1.js @@ -33,7 +33,7 @@ export default observer(function Ext2Setup1() { checked={info['is_audit']} onChange={v => info['is_audit'] = v}/> - + info['rst_notify']['mode'] = v}> diff --git a/spug_web/src/pages/deploy/request/Form.js b/spug_web/src/pages/deploy/request/Form.js deleted file mode 100644 index 16ddac8..0000000 --- a/spug_web/src/pages/deploy/request/Form.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug - * Copyright (c) - * 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 = ( -
- - this.setState({type: val.target.value})}/> - -
- ); - - render() { - const info = store.record; - const {getFieldDecorator} = this.props.form; - return ( - store.formVisible = false} - confirmLoading={this.state.loading} - onOk={this.handleSubmit}> -
- - - {getFieldDecorator('type', {initialValue: info['type']})( - - )} - - - - - - - {getFieldDecorator('name', {initialValue: info['name']})( - - )} - - - this.setState({body: val})} - height="300px"/> - - - {getFieldDecorator('desc', {initialValue: info['desc']})( - - )} - -
-
- ) - } -} - -export default Form.create()(ComForm)