From 54cb817fc4d1a520d38dfc00af3e1714ffe9470c Mon Sep 17 00:00:00 2001 From: vapao Date: Mon, 22 Mar 2021 23:18:03 +0800 Subject: [PATCH] update --- spug_api/apps/deploy/urls.py | 1 + spug_api/apps/deploy/utils.py | 31 ++- spug_api/apps/deploy/views.py | 99 +++++---- spug_web/src/pages/deploy/app/Ext2Form.js | 5 +- spug_web/src/pages/deploy/app/Ext2Setup1.js | 13 +- spug_web/src/pages/deploy/app/Ext2Setup2.js | 210 ++++++++++++++---- spug_web/src/pages/deploy/app/Ext2Setup3.js | 196 ---------------- .../src/pages/deploy/request/Ext1Console.js | 2 +- spug_web/src/pages/deploy/request/Ext1Form.js | 4 +- .../src/pages/deploy/request/Ext2Console.js | 153 +++++++++++++ spug_web/src/pages/deploy/request/Ext2Form.js | 51 ++--- spug_web/src/pages/deploy/request/Table.js | 23 +- spug_web/src/pages/deploy/request/index.js | 7 +- .../pages/deploy/request/index.module.less | 8 + 14 files changed, 444 insertions(+), 359 deletions(-) delete mode 100644 spug_web/src/pages/deploy/app/Ext2Setup3.js create mode 100644 spug_web/src/pages/deploy/request/Ext2Console.js diff --git a/spug_api/apps/deploy/urls.py b/spug_api/apps/deploy/urls.py index abf4245..6a63fd1 100644 --- a/spug_api/apps/deploy/urls.py +++ b/spug_api/apps/deploy/urls.py @@ -8,6 +8,7 @@ from .views import * urlpatterns = [ path('request/', RequestView.as_view()), path('request/1/', post_request_1), + path('request/2/', post_request_2), path('request/upload/', do_upload), path('request//', RequestDetailView.as_view()), ] diff --git a/spug_api/apps/deploy/utils.py b/spug_api/apps/deploy/utils.py index f899b58..04c1a29 100644 --- a/spug_api/apps/deploy/utils.py +++ b/spug_api/apps/deploy/utils.py @@ -29,7 +29,6 @@ def dispatch(req): api_token = uuid.uuid4().hex rds.setex(api_token, 60 * 60, f'{req.deploy.app_id},{req.deploy.env_id}') helper = Helper(rds, rds_key) - # helper.send_step('local', 1, f'完成\r\n{human_time()} 发布准备... ') env = AttrDict( SPUG_APP_NAME=req.deploy.app.name, SPUG_APP_ID=str(req.deploy.app_id), @@ -80,18 +79,16 @@ def _ext1_deploy(req, helper, env): def _ext2_deploy(req, helper, env): - extend = req.deploy.extend_obj - extras = json.loads(req.extra) + helper.send_info('local', f'完成\r\n') + extend, step = req.deploy.extend_obj, 1 host_actions = json.loads(extend.host_actions) server_actions = json.loads(extend.server_actions) - if extras and extras[0]: - env.update({'SPUG_RELEASE': extras[0]}) - step = 2 + env.update({'SPUG_RELEASE': req.version}) for action in server_actions: - helper.send_step('local', step, f'\r\n{human_time()} {action["title"]}...\r\n') + helper.send_step('local', step, f'{human_time()} {action["title"]}...\r\n') helper.local(f'cd /tmp && {action["data"]}', env) step += 1 - helper.send_step('local', 100, '完成\r\n' if step == 2 else '\r\n') + helper.send_step('local', 100, '\r\n') tmp_transfer_file = None for action in host_actions: @@ -119,17 +116,18 @@ def _ext2_deploy(req, helper, env): else: excludes.append(f'--exclude={x}') exclude = ' '.join(excludes) - tar_gz_file = f'{env.SPUG_VERSION}.tar.gz' + tar_gz_file = f'{env.spug_version}.tar.gz' helper.local(f'cd {sp_dir} && tar zcf {tar_gz_file} {exclude} {contain}') helper.send_info('local', '完成\r\n') tmp_transfer_file = os.path.join(sp_dir, tar_gz_file) break if host_actions: threads, latest_exception = [], None - with futures.ThreadPoolExecutor(max_workers=min(10, os.cpu_count() + 5)) as executor: + max_workers = min(10, os.cpu_count() * 4) if req.deploy.is_parallel else 1 + with futures.ThreadPoolExecutor(max_workers=max_workers) as executor: for h_id in json.loads(req.host_ids): env = AttrDict(env.items()) - t = executor.submit(_deploy_ext2_host, helper, h_id, host_actions, env) + t = executor.submit(_deploy_ext2_host, helper, h_id, host_actions, env, req.spug_version) t.h_id = h_id threads.append(t) for t in futures.as_completed(threads): @@ -190,23 +188,22 @@ def _deploy_ext1_host(req, helper, h_id, env): command = f'cd {extend.dst_dir} ; {extend.hook_post_host}' helper.remote(host.id, ssh, command, env) - helper.send_step(h_id, 5, f'\r\n{human_time()} ** 发布成功 **') + helper.send_step(h_id, 100, f'\r\n{human_time()} ** 发布成功 **') -def _deploy_ext2_host(helper, h_id, actions, env): - helper.send_step(h_id, 1, f'{human_time()} 数据准备... ') +def _deploy_ext2_host(helper, h_id, actions, env, spug_version): + helper.send_info(h_id, '就绪\r\n') host = Host.objects.filter(pk=h_id).first() if not host: helper.send_error(h_id, 'no such host') env.update({'SPUG_HOST_ID': h_id, 'SPUG_HOST_NAME': host.hostname}) ssh = host.get_ssh() - helper.send_step(h_id, 2, '完成\r\n') for index, action in enumerate(actions): - helper.send_step(h_id, 2 + index, f'{human_time()} {action["title"]}...\r\n') + helper.send_step(h_id, 1 + index, f'{human_time()} {action["title"]}...\r\n') if action.get('type') == 'transfer': if action.get('src_mode') == '1': try: - ssh.put_file(os.path.join(REPOS_DIR, env.SPUG_DEPLOY_ID, env.SPUG_VERSION), action['dst']) + ssh.put_file(os.path.join(REPOS_DIR, env.SPUG_DEPLOY_ID, spug_version), action['dst']) except Exception as e: helper.send_error(host.id, f'exception: {e}') helper.send_info(host.id, 'transfer completed\r\n') diff --git a/spug_api/apps/deploy/views.py b/spug_api/apps/deploy/views.py index 7f2a8ff..e9b3458 100644 --- a/spug_api/apps/deploy/views.py +++ b/spug_api/apps/deploy/views.py @@ -43,6 +43,7 @@ class RequestView(View): tmp['app_name'] = item.app_name tmp['app_extend'] = item.app_extend tmp['host_ids'] = json.loads(item.host_ids) + tmp['extra'] = json.loads(item.extra) if item.extra else None tmp['rep_extra'] = json.loads(item.rep_extra) if item.rep_extra else None tmp['app_host_ids'] = json.loads(item.app_host_ids) tmp['status_alias'] = item.get_status_display() @@ -50,45 +51,6 @@ class RequestView(View): data.append(tmp) return json_response(data) - def post(self, request): - form, error = JsonParser( - Argument('id', type=int, required=False), - Argument('deploy_id', type=int, help='缺少必要参数'), - Argument('name', help='请输申请标题'), - Argument('extra', type=list, help='缺少必要参数'), - Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'), - Argument('desc', required=False), - ).parse(request.body) - if error is None: - deploy = Deploy.objects.filter(pk=form.deploy_id).first() - if not deploy: - return json_response(error='未找到该发布配置') - if deploy.extend == '2': - if form.extra[0]: - form.extra[0] = form.extra[0].replace("'", '') - if DeployExtend2.objects.filter(deploy=deploy, host_actions__contains='"src_mode": "1"').exists(): - if len(form.extra) < 2: - return json_response(error='该应用的发布配置中使用了数据传输动作且设置为发布时上传,请上传要传输的数据') - form.version = form.extra[1].get('path') - form.name = form.name.replace("'", '') - form.status = '0' if deploy.is_audit else '1' - 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: - 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): form, error = JsonParser( Argument('id', type=int, help='缺少必要参数'), @@ -162,11 +124,12 @@ class RequestDetailView(View): if not req: return json_response(error='未找到指定发布申请') hosts = Host.objects.filter(id__in=json.loads(req.host_ids)) - server_actions, host_actions = [], [] outputs = {x.id: {'id': x.id, 'title': x.name, 'data': []} for x in hosts} + response = {'outputs': outputs, 'status': req.status} if req.deploy.extend == '2': - server_actions = json.loads(req.deploy.extend_obj.server_actions) - host_actions = json.loads(req.deploy.extend_obj.host_actions) + outputs['local'] = {'id': 'local', 'data': []} + response['s_actions'] = json.loads(req.deploy.extend_obj.server_actions) + response['h_actions'] = json.loads(req.deploy.extend_obj.host_actions) rds, key, counter = get_redis_connection(), f'{settings.REQUEST_KEY}:{r_id}', 0 data = rds.lrange(key, counter, counter + 9) while data: @@ -180,12 +143,7 @@ class RequestDetailView(View): if 'status' in item: outputs[item['key']]['status'] = item['status'] data = rds.lrange(key, counter, counter + 9) - return json_response({ - 'server_actions': server_actions, - 'host_actions': host_actions, - 'outputs': outputs, - 'status': req.status - }) + return json_response(response) def post(self, request, r_id): query = {'pk': r_id} @@ -206,7 +164,13 @@ class RequestDetailView(View): req.do_by = request.user req.save() Thread(target=dispatch, args=(req,)).start() - return json_response({'type': req.type, 'outputs': outputs}) + if req.deploy.extend == '2': + message = f'{human_time()} 建立连接... ' + outputs['local'] = {'id': 'local', 'step': 0, 'data': [message]} + s_actions = json.loads(req.deploy.extend_obj.server_actions) + h_actions = json.loads(req.deploy.extend_obj.host_actions) + return json_response({'s_actions': s_actions, 'h_actions': h_actions, 'outputs': outputs}) + return json_response({'outputs': outputs}) def patch(self, request, r_id): form, error = JsonParser( @@ -261,6 +225,43 @@ def post_request_1(request): return json_response(error=error) +def post_request_2(request): + form, error = JsonParser( + Argument('id', type=int, required=False), + Argument('deploy_id', type=int, help='缺少必要参数'), + Argument('name', help='请输申请标题'), + Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择要部署的主机'), + Argument('extra', type=dict, required=False), + Argument('version', required=False), + Argument('desc', required=False), + ).parse(request.body) + if error is None: + deploy = Deploy.objects.filter(pk=form.deploy_id).first() + if not deploy: + return json_response(error='未找到该发布配置') + extra = form.pop('extra') + if DeployExtend2.objects.filter(deploy=deploy, host_actions__contains='"src_mode": "1"').exists(): + if not extra: + return json_response(error='该应用的发布配置中使用了数据传输动作且设置为发布时上传,请上传要传输的数据') + form.spug_version = extra['path'] + form.extra = json.dumps(extra) + else: + form.spug_version = Repository.make_spug_version(deploy.id) + form.name = form.name.replace("'", '') + form.status = '0' if deploy.is_audit else '1' + 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: + 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 do_upload(request): repos_dir = settings.REPOS_DIR file = request.FILES['file'] diff --git a/spug_web/src/pages/deploy/app/Ext2Form.js b/spug_web/src/pages/deploy/app/Ext2Form.js index 34db9dc..7d6d9f5 100644 --- a/spug_web/src/pages/deploy/app/Ext2Form.js +++ b/spug_web/src/pages/deploy/app/Ext2Form.js @@ -9,7 +9,6 @@ import { Modal, Steps } from 'antd'; import styles from './index.module.css'; import Setup1 from './Ext2Setup1'; import Setup2 from './Ext2Setup2'; -import Setup3 from './Ext2Setup3'; import store from './store'; export default observer(function Ext2From() { @@ -30,12 +29,10 @@ export default observer(function Ext2From() { footer={null}> - - + {store.page === 0 && } {store.page === 1 && } - {store.page === 2 && } ) }) diff --git a/spug_web/src/pages/deploy/app/Ext2Setup1.js b/spug_web/src/pages/deploy/app/Ext2Setup1.js index 8c5395c..b535184 100644 --- a/spug_web/src/pages/deploy/app/Ext2Setup1.js +++ b/spug_web/src/pages/deploy/app/Ext2Setup1.js @@ -6,12 +6,14 @@ import React, { useState, useEffect } from 'react'; import { observer } from 'mobx-react'; import { Link } from 'react-router-dom'; -import { Form, Switch, Select, Button, Input, Radio } from "antd"; +import { Form, Switch, Select, Button, Input, Radio } from 'antd'; import envStore from 'pages/config/environment/store'; +import Selector from 'pages/host/Selector'; import store from './store'; export default observer(function Ext2Setup1() { const [envs, setEnvs] = useState([]); + const [selectorVisible, setSelectorVisible] = useState(false); function updateEnvs() { const ids = store.currentRecord['deploys'].map(x => x.env_id); @@ -41,6 +43,10 @@ export default observer(function Ext2Setup1() { 新建环境 + + {info.host_ids.length > 0 && `已选择 ${info.host_ids.length} 台`} + + info.is_parallel = e.target.value}> 并行 @@ -82,6 +88,11 @@ export default observer(function Ext2Setup1() { disabled={!info.env_id} onClick={() => store.page += 1}>下一步 + setSelectorVisible(false)} + onOk={(_, ids) => info.host_ids = ids}/> ) }) diff --git a/spug_web/src/pages/deploy/app/Ext2Setup2.js b/spug_web/src/pages/deploy/app/Ext2Setup2.js index f3ffb27..909b335 100644 --- a/spug_web/src/pages/deploy/app/Ext2Setup2.js +++ b/spug_web/src/pages/deploy/app/Ext2Setup2.js @@ -6,56 +6,186 @@ import React from 'react'; import { observer } from 'mobx-react'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; -import { Form, Select, Button } from 'antd'; -import { hasHostPermission } from 'libs'; -import store from './store'; -import hostStore from 'pages/host/store'; +import { Form, Input, Button, message, Divider, Alert, Select } from 'antd'; +import Editor from 'react-ace'; +import 'ace-builds/src-noconflict/mode-sh'; +import 'ace-builds/src-noconflict/theme-tomorrow'; import styles from './index.module.css'; +import { http, cleanCommand } from 'libs'; +import store from './store'; +import lds from 'lodash'; @observer class Ext2Setup2 extends React.Component { - componentDidMount() { - if (hostStore.records.length === 0) { - hostStore.fetchRecords() + constructor(props) { + super(props); + this.helpMap = { + '0': null, + '1': '相对于输入的本地路径的文件路径,仅将匹配到文件传输至要发布的目标主机。', + '2': '支持模糊匹配,如果路径以 / 开头则基于输入的本地路径匹配,匹配到文件将不会被传输。' + } + this.state = { + loading: false, } } - render() { + handleSubmit = () => { + this.setState({loading: true}); const info = store.deploy; + info['app_id'] = store.app_id; + info['extend'] = '2'; + info['host_actions'] = info['host_actions'].filter(x => (x.title && x.data) || (x.title && (x.src || x.src_mode === '1') && x.dst)); + info['server_actions'] = info['server_actions'].filter(x => x.title && x.data); + http.post('/api/app/deploy/', info) + .then(res => { + message.success('保存成功'); + store.ext2Visible = false; + store.loadDeploys(store.app_id) + }, () => this.setState({loading: false})) + }; + + render() { + const server_actions = store.deploy['server_actions']; + const host_actions = store.deploy['host_actions']; return ( -
- - {info['host_ids'].map((id, index) => ( - - - {!store.isReadOnly && info['host_ids'].length > 1 && ( - store.delHost(index)} /> - )} - - ))} - + + {store.deploy.id === undefined && ( + Spug 将遵循先本地后目标主机的原则,按照顺序依次执行添加的动作,例如:本地动作1 -> 本地动作2 -> 目标主机动作1 -> 目标主机动作2 ...

, +

执行的命令内可以使用发布申请中设置的环境变量 SPUG_RELEASE,一般可用于标记一次发布的版本号或提交ID等,在执行的脚本内通过使用 $SPUG_RELEASE + 获取其值来执行相应操作。

+ ]}/> + )} + {server_actions.map((item, index) => ( +
+ + item['title'] = e.target.value} + placeholder="请输入"/> + + + + item['data'] = cleanCommand(v)} + placeholder="请输入要执行的动作"/> + + {!store.isReadOnly && ( +
server_actions.splice(index, 1)}> + 移除 +
+ )} +
+ ))} + {!store.isReadOnly && ( + + + + )} + + {host_actions.map((item, index) => ( +
+ + item['title'] = e.target.value} + placeholder="请输入"/> + + {item['type'] === 'transfer' ? ([ + + item['src'] = e.target.value} + addonBefore={( + + )}/> + , + [undefined, '0'].includes(item['src_mode']) ? ( + + item['rule'] = e.target.value.replace(',', ',')} + disabled={store.isReadOnly || item['mode'] === '0'} + addonBefore={( + + )}/> + + ) : null, + 使用前请务必阅读官方文档。}> + item['dst'] = e.target.value}/> + + ]) : ( + + item['data'] = cleanCommand(v)} + placeholder="请输入要执行的动作"/> + + )} + {!store.isReadOnly && ( +
host_actions.splice(index, 1)}> + 移除 +
+ )} +
+ ))} + {!store.isReadOnly && ( + + + + + )} - - - - + diff --git a/spug_web/src/pages/deploy/app/Ext2Setup3.js b/spug_web/src/pages/deploy/app/Ext2Setup3.js deleted file mode 100644 index 0a9ec1d..0000000 --- a/spug_web/src/pages/deploy/app/Ext2Setup3.js +++ /dev/null @@ -1,196 +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 { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; -import { Form, Input, Button, message, Divider, Alert, Select } from 'antd'; -import Editor from 'react-ace'; -import 'ace-builds/src-noconflict/mode-sh'; -import 'ace-builds/src-noconflict/theme-tomorrow'; -import styles from './index.module.css'; -import { http, cleanCommand } from 'libs'; -import store from './store'; -import lds from 'lodash'; - -@observer -class Ext2Setup3 extends React.Component { - constructor(props) { - super(props); - this.helpMap = { - '0': null, - '1': '相对于输入的本地路径的文件路径,仅将匹配到文件传输至要发布的目标主机。', - '2': '支持模糊匹配,如果路径以 / 开头则基于输入的本地路径匹配,匹配到文件将不会被传输。' - } - this.state = { - loading: false, - } - } - - handleSubmit = () => { - this.setState({loading: true}); - const info = store.deploy; - info['app_id'] = store.app_id; - info['extend'] = '2'; - info['host_actions'] = info['host_actions'].filter(x => (x.title && x.data) || (x.title && (x.src || x.src_mode === '1') && x.dst)); - info['server_actions'] = info['server_actions'].filter(x => x.title && x.data); - http.post('/api/app/deploy/', info) - .then(res => { - message.success('保存成功'); - store.ext2Visible = false; - store.loadDeploys(store.app_id) - }, () => this.setState({loading: false})) - }; - - render() { - const server_actions = store.deploy['server_actions']; - const host_actions = store.deploy['host_actions']; - return ( -
- {store.deploy.id === undefined && ( - Spug 将遵循先本地后目标主机的原则,按照顺序依次执行添加的动作,例如:本地动作1 -> 本地动作2 -> 目标主机动作1 -> 目标主机动作2 ...

, -

执行的命令内可以使用发布申请中设置的环境变量 SPUG_RELEASE,一般可用于标记一次发布的版本号或提交ID等,在执行的脚本内通过使用 $SPUG_RELEASE - 获取其值来执行相应操作。

- ]}/> - )} - {server_actions.map((item, index) => ( -
- - item['title'] = e.target.value} - placeholder="请输入"/> - - - - item['data'] = cleanCommand(v)} - placeholder="请输入要执行的动作"/> - - {!store.isReadOnly && ( -
server_actions.splice(index, 1)}> - 移除 -
- )} -
- ))} - {!store.isReadOnly && ( - - - - )} - - {host_actions.map((item, index) => ( -
- - item['title'] = e.target.value} - placeholder="请输入"/> - - {item['type'] === 'transfer' ? ([ - - item['src'] = e.target.value} - addonBefore={( - - )}/> - , - [undefined, '0'].includes(item['src_mode']) ? ( - - item['rule'] = e.target.value.replace(',', ',')} - disabled={store.isReadOnly || item['mode'] === '0'} - addonBefore={( - - )}/> - - ) : null, - 使用前请务必阅读官方文档。}> - item['dst'] = e.target.value}/> - - ]) : ( - - item['data'] = cleanCommand(v)} - placeholder="请输入要执行的动作"/> - - )} - {!store.isReadOnly && ( -
host_actions.splice(index, 1)}> - 移除 -
- )} -
- ))} - {!store.isReadOnly && ( - - - - - )} - - - - - - ) - } -} - -export default Ext2Setup3 diff --git a/spug_web/src/pages/deploy/request/Ext1Console.js b/spug_web/src/pages/deploy/request/Ext1Console.js index d304d12..a233713 100644 --- a/spug_web/src/pages/deploy/request/Ext1Console.js +++ b/spug_web/src/pages/deploy/request/Ext1Console.js @@ -85,7 +85,7 @@ function Ext1Console(props) { + status={item.step === 100 ? 'success' : item.status === 'error' ? 'exception' : 'active'}/> ))} ) : ( diff --git a/spug_web/src/pages/deploy/request/Ext1Form.js b/spug_web/src/pages/deploy/request/Ext1Form.js index c04d9fc..5fcace5 100644 --- a/spug_web/src/pages/deploy/request/Ext1Form.js +++ b/spug_web/src/pages/deploy/request/Ext1Form.js @@ -72,8 +72,8 @@ export default observer(function () { ))} - - {host_ids.length > 0 && `已选择 ${host_ids.length} 台`} + + {host_ids.length > 0 && `已选择 ${host_ids.length} 台(可选${app_host_ids.length})`} diff --git a/spug_web/src/pages/deploy/request/Ext2Console.js b/spug_web/src/pages/deploy/request/Ext2Console.js new file mode 100644 index 0000000..651b2a7 --- /dev/null +++ b/spug_web/src/pages/deploy/request/Ext2Console.js @@ -0,0 +1,153 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React, { useEffect, useState } from 'react'; +import { observer, useLocalStore } from 'mobx-react'; +import { Card, Progress, Modal, Collapse, Steps } from 'antd'; +import { ShrinkOutlined, CaretRightOutlined, LoadingOutlined, CloseOutlined } from '@ant-design/icons'; +import OutView from './OutView'; +import { http, X_TOKEN } from 'libs'; +import styles from './index.module.less'; +import store from './store'; + +function Ext2Console(props) { + const outputs = useLocalStore(() => ({local: {id: 'local'}})); + const [sActions, setSActions] = useState([]); + const [hActions, setHActions] = useState([]); + + useEffect(props.request.mode === 'read' ? readDeploy : doDeploy, []) + + function readDeploy() { + let socket; + http.get(`/api/deploy/request/${props.request.id}/`) + .then(res => { + setSActions(res.s_actions); + setHActions(res.h_actions); + Object.assign(outputs, res.outputs) + if (res.status === '2') { + socket = _makeSocket() + } + }) + return () => socket && socket.close() + } + + function doDeploy() { + let socket; + http.post(`/api/deploy/request/${props.request.id}/`) + .then(res => { + setSActions(res.s_actions); + setHActions(res.h_actions); + Object.assign(outputs, res.outputs) + socket = _makeSocket() + }) + return () => socket && socket.close() + } + + function _makeSocket() { + let index = 0; + const token = props.request.id; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/request/${token}/?x-token=${X_TOKEN}`); + socket.onopen = () => socket.send(String(index)); + socket.onmessage = e => { + if (e.data === 'pong') { + socket.send(String(index)) + } else { + index += 1; + const {key, data, step, status} = JSON.parse(e.data); + if (data !== undefined) outputs[key].data.push(data); + if (step !== undefined) outputs[key].step = step; + if (status !== undefined) outputs[key].status = status; + } + } + return socket + } + + function StepItem(props) { + let icon = null; + if (props.step === props.item.step && props.item.status !== 'error') { + if (props.item.id === 'local' || outputs.local.step === 100) { + icon = + } + } + return + } + + function switchMiniMode() { + const value = store.tabModes[props.request.id]; + store.tabModes[props.request.id] = !value + } + + return store.tabModes[props.request.id] ? ( + +
+
{props.request.name}
+ store.showConsole(props.request, true)}/> +
+ + {Object.values(outputs).filter(x => x.id !== 'local').map(item => ( + + ))} +
+ ) : ( + store.showConsole(props.request, true)} + title={[ + {props.request.name}, +
+ +
+ ]}> + + + + {sActions.map((item, index) => ( + + ))} + + )}> + + + + }> + {Object.values(outputs).filter(x => x.id !== 'local').map((item, index) => ( + + {item.title}{item.step} + + + {hActions.map((action, index) => ( + + ))} + + }> + + + ))} + +
+ ) +} + +export default observer(Ext2Console) \ No newline at end of file diff --git a/spug_web/src/pages/deploy/request/Ext2Form.js b/spug_web/src/pages/deploy/request/Ext2Form.js index ef1c8e7..1eb395a 100644 --- a/spug_web/src/pages/deploy/request/Ext2Form.js +++ b/spug_web/src/pages/deploy/request/Ext2Form.js @@ -8,10 +8,11 @@ import { observer } from 'mobx-react'; import { UploadOutlined } from '@ant-design/icons'; import { Modal, Form, Input, Upload, message, Button } from 'antd'; import hostStore from 'pages/host/store'; +import HostSelector from './HostSelector'; import { http, X_TOKEN } from 'libs'; +import styles from './index.module.less'; import store from './store'; import lds from 'lodash'; -import HostSelector from "./HostSelector"; export default observer(function () { const [form] = Form.useForm(); @@ -19,17 +20,13 @@ export default observer(function () { const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); const [fileList, setFileList] = useState([]); - const [host_ids, setHostIds] = useState(lds.clone(store.record.app_host_ids)); + const [host_ids, setHostIds] = useState([]); useEffect(() => { - if (hostStore.records.length === 0) { - hostStore.fetchRecords() - } - const file = lds.get(store, 'record.extra.1'); - if (file) { - file.uid = '0'; - setFileList([file]) - } + const {app_host_ids, host_ids, extra} = store.record; + setHostIds(lds.clone(host_ids || app_host_ids)); + if (hostStore.records.length === 0) hostStore.fetchRecords(); + if (store.record.extra) setFileList([{...extra, uid: '0'}]) }, []) function handleSubmit() { @@ -39,13 +36,10 @@ export default observer(function () { setLoading(true); const formData = form.getFieldsValue(); formData['id'] = store.record.id; - formData['deploy_id'] = store.record.deploy_id; - formData['extra'] = [formData['extra']]; - if (fileList.length > 0) { - formData['extra'].push(lds.pick(fileList[0], ['path', 'name'])) - } formData['host_ids'] = host_ids; - http.post('/api/deploy/request/', formData) + formData['deploy_id'] = store.record.deploy_id; + if (fileList.length > 0) formData['extra'] = lds.pick(fileList[0], ['path', 'name']); + http.post('/api/deploy/request/2/', formData) .then(res => { message.success('操作成功'); store.ext2Visible = false; @@ -73,6 +67,7 @@ export default observer(function () { return false } + const {app_host_ids, deploy_id} = store.record; return ( store.ext2Visible = false} confirmLoading={loading} onOk={handleSubmit}> -
- + + + name="version" + label="SPUG_RELEASE" + tooltip="可以在自定义脚本中引用该变量,用于设置本次发布相关的动态变量,在脚本中通过 $SPUG_RELEASE 来使用该值。"> - + + data={{deploy_id}} onChange={handleUploadChange}> {fileList.length === 0 ? : null} - - {host_ids.length > 0 && `已选择 ${host_ids.length} 台`} + + {host_ids.length > 0 && `已选择 ${host_ids.length} 台(可选${app_host_ids.length})`} + + + {visible && setVisible(false)} onOk={ids => setHostIds(ids)}/>}
diff --git a/spug_web/src/pages/deploy/request/Table.js b/spug_web/src/pages/deploy/request/Table.js index f43e825..50b5e49 100644 --- a/spug_web/src/pages/deploy/request/Table.js +++ b/spug_web/src/pages/deploy/request/Table.js @@ -40,7 +40,7 @@ function ComTable() { } else { return ( - {info.extra[0]} + {info.version} ) } @@ -96,9 +96,7 @@ function ComTable() { ; case '3': return - 查看 + store.readConsole(info)}>查看 { - Modal.confirm({ - title: '回滚确认', - content: `确定要回滚至 ${res['date']} 创建的名称为【${res['name']}】的发布申请版本?`, - onOk: () => { - return http.put('/api/deploy/request/', {id: info.id, action: 'do'}) - .then(() => { - message.success('回滚申请创建成功'); - store.fetchRecords() - }) - } - }) - }) - } - function handleDelete(info) { Modal.confirm({ title: '删除确认', diff --git a/spug_web/src/pages/deploy/request/index.js b/spug_web/src/pages/deploy/request/index.js index 1251b07..d41bff5 100644 --- a/spug_web/src/pages/deploy/request/index.js +++ b/spug_web/src/pages/deploy/request/index.js @@ -13,6 +13,7 @@ import Ext2Form from './Ext2Form'; import Approve from './Approve'; import ComTable from './Table'; import Ext1Console from './Ext1Console'; +import Ext2Console from './Ext2Console'; import { http, includes } from 'libs'; import envStore from 'pages/config/environment/store'; import appStore from 'pages/config/app/store'; @@ -115,7 +116,11 @@ function Index() { {store.tabs.map(item => ( - + {item.app_extend === '1' ? ( + + ) : ( + + )} ))} diff --git a/spug_web/src/pages/deploy/request/index.module.less b/spug_web/src/pages/deploy/request/index.module.less index d580bb2..3f8ae9a 100644 --- a/spug_web/src/pages/deploy/request/index.module.less +++ b/spug_web/src/pages/deploy/request/index.module.less @@ -19,6 +19,10 @@ box-shadow: 0 0 4px rgba(0, 0, 0, .3); border-radius: 5px; + :global(.ant-progress-text) { + text-align: center; + } + .header { display: flex; justify-content: space-between; @@ -100,6 +104,10 @@ padding: 0; } +.upload :global(.ant-upload-list-item) { + margin: 0; +} + .floatBox { display: none; position: fixed;