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,65 +302,33 @@ 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)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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