upgrade deploy

pull/289/head
vapao 2021-02-07 23:52:46 +08:00
parent d076c51c3d
commit 4d86288ab5
9 changed files with 171 additions and 284 deletions

View File

@ -74,10 +74,7 @@ class DeployExtend1(models.Model, ModelMixin):
deploy = models.OneToOneField(Deploy, primary_key=True, on_delete=models.CASCADE) deploy = models.OneToOneField(Deploy, primary_key=True, on_delete=models.CASCADE)
git_repo = models.CharField(max_length=255) git_repo = models.CharField(max_length=255)
dst_dir = models.CharField(max_length=255) dst_dir = models.CharField(max_length=255)
dst_repo = models.CharField(max_length=255)
versions = models.IntegerField()
filter_rule = models.TextField() filter_rule = models.TextField()
custom_envs = models.TextField()
hook_pre_server = models.TextField(null=True) hook_pre_server = models.TextField(null=True)
hook_post_server = models.TextField(null=True) hook_post_server = models.TextField(null=True)
hook_pre_host = models.TextField(null=True) hook_pre_host = models.TextField(null=True)
@ -86,7 +83,6 @@ class DeployExtend1(models.Model, ModelMixin):
def to_dict(self, *args, **kwargs): def to_dict(self, *args, **kwargs):
tmp = super().to_dict(*args, **kwargs) tmp = super().to_dict(*args, **kwargs)
tmp['filter_rule'] = json.loads(self.filter_rule) tmp['filter_rule'] = json.loads(self.filter_rule)
tmp['custom_envs'] = '\n'.join(f'{k}={v}' for k, v in json.loads(self.custom_envs).items())
return tmp return tmp
def __repr__(self): def __repr__(self):

View File

@ -120,10 +120,7 @@ class DeployView(View):
extend_form, error = JsonParser( extend_form, error = JsonParser(
Argument('git_repo', handler=str.strip, help='请输入git仓库地址'), Argument('git_repo', handler=str.strip, help='请输入git仓库地址'),
Argument('dst_dir', handler=str.strip, help='请输入发布目标路径'), Argument('dst_dir', handler=str.strip, help='请输入发布目标路径'),
Argument('dst_repo', handler=str.strip, help='请输入目标仓库路径'),
Argument('versions', type=int, help='请输入保留历史版本数量'),
Argument('filter_rule', type=dict, help='参数错误'), Argument('filter_rule', type=dict, help='参数错误'),
Argument('custom_envs', handler=str.strip, required=False),
Argument('hook_pre_server', handler=str.strip, default=''), Argument('hook_pre_server', handler=str.strip, default=''),
Argument('hook_post_server', handler=str.strip, default=''), Argument('hook_post_server', handler=str.strip, default=''),
Argument('hook_pre_host', handler=str.strip, default=''), Argument('hook_pre_host', handler=str.strip, default=''),
@ -133,7 +130,6 @@ class DeployView(View):
return json_response(error=error) return json_response(error=error)
extend_form.dst_dir = extend_form.dst_dir.rstrip('/') extend_form.dst_dir = extend_form.dst_dir.rstrip('/')
extend_form.filter_rule = json.dumps(extend_form.filter_rule) extend_form.filter_rule = json.dumps(extend_form.filter_rule)
extend_form.custom_envs = json.dumps(parse_envs(extend_form.custom_envs))
if form.id: if form.id:
extend = DeployExtend1.objects.filter(deploy_id=form.id).first() extend = DeployExtend1.objects.filter(deploy_id=form.id).first()
if extend.git_repo != extend_form.git_repo: if extend.git_repo != extend_form.git_repo:

View File

@ -19,8 +19,7 @@ class AddSelect extends React.Component {
git_type: 'branch', git_type: 'branch',
is_audit: false, is_audit: false,
rst_notify: {mode: '0'}, rst_notify: {mode: '0'},
versions: 10, host_ids: [],
host_ids: [undefined],
filter_rule: {type: 'contain', data: ''} filter_rule: {type: 'contain', data: ''}
} }
}; };

View File

@ -23,15 +23,15 @@ export default observer(function Ext1From() {
return ( return (
<Modal <Modal
visible visible
width={900} width={800}
maskClosable={false} maskClosable={false}
title={title} title={title}
onCancel={() => store.ext1Visible = false} onCancel={() => store.ext1Visible = false}
footer={null}> footer={null}>
<Steps current={store.page} className={styles.steps}> <Steps current={store.page} className={styles.steps}>
<Steps.Step key={0} title="基本配置"/> <Steps.Step key={0} title="基本配置"/>
<Steps.Step key={1} title="发布主机"/> <Steps.Step key={1} title="构建配置"/>
<Steps.Step key={2} title="任务配置"/> <Steps.Step key={2} title="发布配置"/>
</Steps> </Steps>
{store.page === 0 && <Setup1/>} {store.page === 0 && <Setup1/>}
{store.page === 1 && <Setup2/>} {store.page === 1 && <Setup2/>}

View File

@ -8,6 +8,7 @@ import { observer } from 'mobx-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Switch, Form, Input, Select, Button } from 'antd'; import { Switch, Form, Input, Select, Button } from 'antd';
import envStore from 'pages/config/environment/store'; import envStore from 'pages/config/environment/store';
import Selector from 'pages/host/Selector';
import store from './store'; import store from './store';
export default observer(function Ext1Setup1() { export default observer(function Ext1Setup1() {
@ -41,6 +42,10 @@ export default observer(function Ext1Setup1() {
<Link disabled={store.isReadOnly} to="/config/environment">新建环境</Link> <Link disabled={store.isReadOnly} to="/config/environment">新建环境</Link>
</Form.Item> </Form.Item>
</Form.Item> </Form.Item>
<Form.Item required label="目标主机">
{info.host_ids.length > 0 && `已选择 ${info.host_ids.length}`}
<Button type="link" onClick={() => store.selectorVisible = true}>选择主机</Button>
</Form.Item>
<Form.Item required label="Git仓库地址"> <Form.Item required label="Git仓库地址">
<Input disabled={store.isReadOnly} value={info['git_repo']} onChange={e => info['git_repo'] = e.target.value} <Input disabled={store.isReadOnly} value={info['git_repo']} onChange={e => info['git_repo'] = e.target.value}
placeholder="请输入Git仓库地址"/> placeholder="请输入Git仓库地址"/>
@ -60,9 +65,10 @@ export default observer(function Ext1Setup1() {
</span>}> </span>}>
<Input <Input
addonBefore={( addonBefore={(
<Select disabled={store.isReadOnly} <Select
value={info['rst_notify']['mode']} style={{width: 100}} disabled={store.isReadOnly}
onChange={v => info['rst_notify']['mode'] = v}> value={info['rst_notify']['mode']} style={{width: 100}}
onChange={v => info['rst_notify']['mode'] = v}>
<Select.Option value="0">关闭</Select.Option> <Select.Option value="0">关闭</Select.Option>
<Select.Option value="1">钉钉</Select.Option> <Select.Option value="1">钉钉</Select.Option>
<Select.Option value="3">企业微信</Select.Option> <Select.Option value="3">企业微信</Select.Option>
@ -77,9 +83,14 @@ export default observer(function Ext1Setup1() {
<Form.Item wrapperCol={{span: 14, offset: 6}}> <Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button <Button
type="primary" type="primary"
disabled={!(info.env_id && info['git_repo'])} disabled={!(info.env_id && info.git_repo && info.host_ids.length)}
onClick={() => store.page += 1}>下一步</Button> onClick={() => store.page += 1}>下一步</Button>
</Form.Item> </Form.Item>
<Selector
visible={store.selectorVisible}
selectedRowKeys={[...info.host_ids]}
onCancel={() => store.selectorVisible = false}
onOk={(_, ids) => info.host_ids = ids}/>
</Form> </Form>
) )
}) })

View File

@ -5,85 +5,99 @@
*/ */
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { QuestionCircleOutlined } from '@ant-design/icons';
import { Form, Input, Select, Button, message } from "antd"; import { Form, Radio, Button, Tooltip } from "antd";
import { hasHostPermission } from 'libs'; import { cleanCommand } from 'libs';
import Editor from 'react-ace';
import 'ace-builds/src-noconflict/mode-text';
import 'ace-builds/src-noconflict/mode-sh';
import 'ace-builds/src-noconflict/theme-tomorrow';
import store from './store'; import store from './store';
import hostStore from 'pages/host/store';
import styles from './index.module.css';
@observer export default observer(function () {
class Ext1Setup2 extends React.Component { const Tips = (
componentDidMount() { <a
if (hostStore.records.length === 0) { target="_blank"
hostStore.fetchRecords() rel="noopener noreferrer"
} href="https://spug.dev/docs/deploy-config/#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F">内置全局变量</a>
)
function handleNext() {
store.page += 1
} }
checkStatus = () => { const FilterHead = (
const info = store.deploy; <div style={{width: 512, display: 'flex', justifyContent: 'space-between'}}>
return info['dst_dir'] && info['dst_repo'] && info['versions'] && info['host_ids'].filter(x => x).length > 0 <span>
}; 文件过滤规则 &nbsp;
<Tooltip title="请输入相对于项目根目录的文件路径,根据包含或排除规则进行打包。">
<QuestionCircleOutlined style={{color: 'rgba(0, 0, 0, 0.45)'}}/>
</Tooltip>
</span>
<Radio.Group
size="small"
value={store.deploy.filter_rule.type}
onChange={e => store.deploy.filter_rule.type = e.target.value}>
<Radio.Button value="contain">
<Tooltip title="仅打包匹配到的文件或目录,如果内容为空则打包所有。">包含</Tooltip>
</Radio.Button>
<Radio.Button value="exclude">
<Tooltip title="打包时排除匹配到的文件或目录,如果内容为空则不排除任何文件。">排除</Tooltip>
</Radio.Button>
</Radio.Group>
</div>
)
handleNext = () => { const info = store.deploy;
const {dst_dir, dst_repo} = store.deploy; return (
if (dst_repo.includes(dst_dir.replace(/\/*$/, '/'))) { <Form layout="vertical" style={{padding: '0 120px'}}>
message.error('仓库目录不能位于发布部署目录内') <Form.Item label={FilterHead} tooltip="xxx">
} else { <Editor
store.page += 1 readOnly={store.isReadOnly}
} mode="text"
}; theme="tomorrow"
width="100%"
render() { height="80px"
const info = store.deploy; placeholder="每行一条规则"
return ( value={info['filter_rule']['data']}
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}> onChange={v => info['filter_rule']['data'] = cleanCommand(v)}
<Form.Item required label="目标主机部署路径" help="目标主机的应用根目录,例如:/var/www/html"> style={{border: '1px solid #e8e8e8'}}/>
<Input disabled={store.isReadOnly} value={info['dst_dir']} onChange={e => info['dst_dir'] = e.target.value} </Form.Item>
placeholder="请输入目标主机部署路径"/> <Form.Item
</Form.Item> label="代码检出前执行"
<Form.Item required label="目标主机仓库路径" help="此目录用于存储应用的历史版本,例如:/data/spug/repos"> tooltip="在运行 Spug 的服务器(或容器)上执行,当前目录为仓库源代码目录,可以执行任意自定义命令。"
<Input disabled={store.isReadOnly} value={info['dst_repo']} onChange={e => info['dst_repo'] = e.target.value} placeholder="请输入目标主机仓库路径"/> help={<span>可使用 {Tips}请避免在此修改已跟踪的文件防止在检出代码时失败</span>}>
</Form.Item> <Editor
<Form.Item required label="保留历史版本数量" help="早于指定数量的历史版本会被删除,以释放空间"> readOnly={store.isReadOnly}
<Input disabled={store.isReadOnly} value={info['versions']} onChange={e => info['versions'] = e.target.value} placeholder="请输入保留历史版本数量"/> mode="sh"
</Form.Item> theme="tomorrow"
<Form.Item required label="发布目标主机"> width="100%"
{info['host_ids'].map((id, index) => ( height="120px"
<React.Fragment key={index}> placeholder="输入要执行的命令"
<Select value={info['hook_pre_server']}
value={id} onChange={v => info['hook_pre_server'] = cleanCommand(v)}
showSearch style={{border: '1px solid #e8e8e8'}}/>
placeholder="请选择" </Form.Item>
disabled={store.isReadOnly} <Form.Item
style={{width: '80%', marginRight: 10, marginBottom: 12}} label="代码检出后执行"
optionFilterProp="children" style={{marginTop: 12, marginBottom: 24}}
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0} tooltip="在运行 Spug 的服务器(或容器)上执行,当前目录为检出后的源代码目录,可执行任意自定义命令。"
onChange={v => store.editHost(index, v)}> help={<span>可使用 {Tips}大多数情况下在此进行构建操作</span>}>
{hostStore.records.filter(x => hasHostPermission(x.id)).map(item => ( <Editor
<Select.Option key={item.id} value={item.id} disabled={info['host_ids'].includes(item.id)}> readOnly={store.isReadOnly}
{`${item.name}(${item['hostname']}:${item['port']})`} mode="sh"
</Select.Option> theme="tomorrow"
))} width="100%"
</Select> height="120px"
{!store.isReadOnly && info['host_ids'].length > 1 && ( placeholder="输入要执行的命令"
<MinusCircleOutlined className={styles.delIcon} onClick={() => store.delHost(index)} /> value={info['hook_post_server']}
)} onChange={v => info['hook_post_server'] = cleanCommand(v)}
</React.Fragment> style={{border: '1px solid #e8e8e8'}}/>
))} </Form.Item>
</Form.Item> <Form.Item wrapperCol={{span: 14, offset: 6}}>
<Form.Item wrapperCol={{span: 14, offset: 6}}> <Button type="primary" onClick={handleNext}>下一步</Button>
<Button disabled={store.isReadOnly} type="dashed" style={{width: '80%'}} onClick={store.addHost}> <Button style={{marginLeft: 20}} onClick={() => store.page -= 1}>上一步</Button>
<PlusOutlined />添加目标主机 </Form.Item>
</Button> </Form>
</Form.Item> )
<Form.Item wrapperCol={{span: 14, offset: 6}}> })
<Button disabled={!this.checkStatus()} type="primary" onClick={this.handleNext}>下一步</Button>
<Button style={{marginLeft: 20}} onClick={() => store.page -= 1}>上一步</Button>
</Form.Item>
</Form>
)
}
}
export default Ext1Setup2

View File

@ -3,210 +3,79 @@
* Copyright (c) <spug.dev@gmail.com> * Copyright (c) <spug.dev@gmail.com>
* Released under the AGPL-3.0 License. * Released under the AGPL-3.0 License.
*/ */
import React from 'react'; import React, {useState} from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { GitlabOutlined, InfoCircleOutlined, SettingOutlined, SwapOutlined } from '@ant-design/icons'; import { Form, Button, Input, message } from 'antd';
import { Form, Row, Col, Button, Radio, Tooltip, message } from 'antd';
import { LinkButton } from 'components';
import Editor from 'react-ace'; import Editor from 'react-ace';
import 'ace-builds/src-noconflict/mode-text'; import 'ace-builds/src-noconflict/mode-text';
import 'ace-builds/src-noconflict/mode-sh'; import 'ace-builds/src-noconflict/mode-sh';
import 'ace-builds/src-noconflict/theme-tomorrow'; import 'ace-builds/src-noconflict/theme-tomorrow';
import { http, cleanCommand } from 'libs';
import store from './store'; import store from './store';
import http from 'libs/http';
import styles from './index.module.css';
import { cleanCommand } from "../../../libs";
@observer export default observer(function () {
class Ext1Setup3 extends React.Component { const [loading, setLoading] = useState(false);
constructor(props) { const Tips = (
super(props); <a
this.helpMap = { target="_blank"
'2': <span> rel="noopener noreferrer"
Spug 内置了一些全局变量这些变量可以直接使用请参考官方文档 href="https://spug.dev/docs/deploy-config/#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F">内置全局变量</a>
<a target="_blank" rel="noopener noreferrer" )
href="https://spug.dev/docs/deploy-config/#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F">全局变量</a>
</span>,
'3': '在部署 Spug 的服务器上运行,可以执行任意自定义命令。',
'4': '在部署 Spug 的服务器上运行,当前目录为检出后待发布的源代码目录,可执行任意自定义命令。',
'5': '在发布的目标主机上运行,当前目录为目标主机上待发布的源代码目录,可执行任意自定义命令。',
'6': '在发布的目标主机上运行,当前目录为已发布的应用目录,可执行任意自定义命令。'
};
this.state = {
loading: false,
full: ''
}
}
handleSubmit = () => { function handleSubmit() {
this.setState({loading: true}); setLoading(true);
const info = store.deploy; const info = store.deploy;
info['app_id'] = store.app_id; info['app_id'] = store.app_id;
info['extend'] = '1'; info['extend'] = '1';
info['host_ids'] = info['host_ids'].filter(x => x);
http.post('/api/app/deploy/', info) http.post('/api/app/deploy/', info)
.then(() => { .then(() => {
message.success('保存成功'); message.success('保存成功');
store.loadDeploys(store.app_id); store.loadDeploys(store.app_id);
store.ext1Visible = false store.ext1Visible = false
}, () => this.setState({loading: false})) }, () => setLoading(false))
};
handleFullscreen = (id) => {
if (this.state.full) {
this.setState({full: ''})
} else {
this.setState({full: id})
}
} }
FilterLabel = (props) => ( const info = store.deploy;
<div style={{display: 'flex', alignItems: 'center', height: 40}}> return (
<div>文件过滤 :</div> <Form layout="vertical" style={{padding: '0 120px'}}>
<Radio.Group <Form.Item required label="部署目标路径" tooltip="应用最终在主机上部署路径,构建的结果将会放置于该路径下。">
disabled={store.isReadOnly} <Input value={info['dst_dir']} onChange={e => info['dst_dir'] = e.target.value} placeholder="请输入部署目标路径" />
style={{marginLeft: 20, float: 'left'}} </Form.Item>
value={props.type} <Form.Item
onChange={e => store.deploy['filter_rule']['type'] = e.target.value}> label="应用发布前执行"
<Radio value="contain">包含 tooltip="在发布的目标主机上运行,当前目录为目标主机上待发布的源代码目录,可执行任意自定义命令。"
<Tooltip title="请输入相对于项目根目录的文件路径,仅将匹配到文件传输至要发布的目标主机。"> help={<span>可使用 {Tips}此时还未进行文件变更可进行一些发布前置操作</span>}>
<InfoCircleOutlined style={{color: '#515151', marginLeft: 8}}/> <Editor
</Tooltip> readOnly={store.isReadOnly}
</Radio> mode="sh"
<Radio value="exclude">排除 theme="tomorrow"
<Tooltip title="支持模糊匹配,如果路径以 / 开头则基于项目根目录匹配,匹配到文件将不会被传输。"> width="100%"
<InfoCircleOutlined style={{color: '#515151', marginLeft: 8}}/> height="150px"
</Tooltip> placeholder="输入要执行的命令"
</Radio> value={info['hook_pre_host']}
</Radio.Group> onChange={v => info['hook_pre_host'] = cleanCommand(v)}
<div style={{flex: 1, textAlign: 'right'}}> style={{border: '1px solid #e8e8e8'}}/>
<LinkButton onClick={() => this.handleFullscreen('1')}>{this.state.full ? '退出全屏' : '全屏'}</LinkButton> </Form.Item>
</div> <Form.Item
</div> label="应用发布后执行"
); style={{marginTop: 12, marginBottom: 24}}
tooltip="在发布的目标主机上运行,当前目录为已发布的应用目录,可执行任意自定义命令。"
NormalLabel = (props) => ( help={<span>可使用 {Tips}可以在发布后进行重启服务等操作</span>}>
<div style={{display: 'flex', alignItems: 'center', height: 40}}> <Editor
<div style={{marginRight: 8}}>{props.title} :</div> readOnly={store.isReadOnly}
<Tooltip title={this.helpMap[props.id]}> mode="sh"
<InfoCircleOutlined style={{color: '#515151'}}/> theme="tomorrow"
</Tooltip> width="100%"
<div style={{flex: 1, textAlign: 'right'}}> height="150px"
<LinkButton onClick={() => this.handleFullscreen(props.id)}>{this.state.full ? '退出全屏' : '全屏'}</LinkButton> placeholder="输入要执行的命令"
</div> value={info['hook_post_host']}
</div> onChange={v => info['hook_post_host'] = cleanCommand(v)}
); style={{border: '1px solid #e8e8e8'}}/>
</Form.Item>
render() { <Form.Item wrapperCol={{span: 14, offset: 6}}>
const info = store.deploy; <Button disabled={store.isReadOnly} loading={loading} type="primary" onClick={handleSubmit}>提交</Button>
const {full} = this.state; <Button style={{marginLeft: 20}} onClick={() => store.page -= 1}>上一步</Button>
return ( </Form.Item>
<React.Fragment> </Form>
<Row> )
<Col span={11}> })
<div className={full === '1' ? styles.fullScreen : null} style={{marginBottom: 24}}>
<this.FilterLabel type={info['filter_rule']['type']}/>
<Editor
readOnly={store.isReadOnly}
mode="text"
theme="tomorrow"
width="100%"
height={full === '1' ? '100vh' : '100px'}
placeholder="每行一条规则"
value={info['filter_rule']['data']}
onChange={v => info['filter_rule']['data'] = cleanCommand(v)}
style={{border: '1px solid #e8e8e8'}}/>
</div>
<div className={full === '3' ? styles.fullScreen : null} style={{marginBottom: 24}}>
<this.NormalLabel title="代码检出前执行" id="3"/>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
height={full === '3' ? '100vh' : '100px'}
placeholder="输入要执行的命令"
value={info['hook_pre_server']}
onChange={v => info['hook_pre_server'] = cleanCommand(v)}
style={{border: '1px solid #e8e8e8'}}/>
</div>
<div className={full === '5' ? styles.fullScreen : null} style={{marginBottom: 24}}>
<this.NormalLabel title="应用发布前执行" id="5"/>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
height={full === '5' ? '100vh' : '100px'}
placeholder="输入要执行的命令"
value={info['hook_pre_host']}
onChange={v => info['hook_pre_host'] = cleanCommand(v)}
style={{border: '1px solid #e8e8e8'}}/>
</div>
</Col>
<Col span={2}>
<div className={styles.deployBlock} style={{marginTop: 39}}>
<SettingOutlined style={{fontSize: 32}}/>
<span style={{fontSize: 12, marginTop: 5}}>基础设置</span>
</div>
<div className={styles.deployBlock}>
<GitlabOutlined style={{fontSize: 32}}/>
<span style={{fontSize: 12, marginTop: 5}}>检出代码</span>
</div>
<div className={styles.deployBlock}>
<SwapOutlined style={{fontSize: 32}}/>
<span style={{fontSize: 12, marginTop: 5}}>版本切换</span>
</div>
</Col>
<Col span={11}>
<div className={full === '2' ? styles.fullScreen : null} style={{marginBottom: 24}}>
<this.NormalLabel title="自定义全局变量" id="2"/>
<Editor
readOnly={store.isReadOnly}
mode="text"
theme="tomorrow"
width="100%"
height={full === '2' ? '100vh' : '100px'}
placeholder="每行一个例如HOME=/data/spug"
value={info['custom_envs']}
onChange={v => info['custom_envs'] = cleanCommand(v)}
style={{border: '1px solid #e8e8e8'}}/>
</div>
<div className={full === '4' ? styles.fullScreen : null} style={{marginBottom: 24}}>
<this.NormalLabel title="代码检出后执行" id="4"/>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
height={full === '4' ? '100vh' : '100px'}
placeholder="输入要执行的命令"
value={info['hook_post_server']}
onChange={v => info['hook_post_server'] = cleanCommand(v)}
style={{border: '1px solid #e8e8e8'}}/>
</div>
<div className={full === '6' ? styles.fullScreen : null} style={{marginBottom: 24}}>
<this.NormalLabel title="应用发布后执行" id="6"/>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
height={full === '6' ? '100vh' : '100px'}
placeholder="输入要执行的命令"
value={info['hook_post_host']}
onChange={v => info['hook_post_host'] = cleanCommand(v)}
style={{border: '1px solid #e8e8e8'}}/>
</div>
</Col>
</Row>
<Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button disabled={store.isReadOnly} type="primary" onClick={this.handleSubmit}>提交</Button>
<Button style={{marginLeft: 20}} onClick={() => store.page -= 1}>上一步</Button>
</Form.Item>
</React.Fragment>
)
}
}
export default Ext1Setup3

View File

@ -91,7 +91,7 @@ class ComTable extends React.Component {
}; };
expandedRowRender = (record) => { expandedRowRender = (record) => {
if (record['deploys'] === undefined) { if (!record.isLoaded) {
store.loadDeploys(record.id) store.loadDeploys(record.id)
} }

View File

@ -18,6 +18,7 @@ class Store {
@observable addVisible = false; @observable addVisible = false;
@observable ext1Visible = false; @observable ext1Visible = false;
@observable ext2Visible = false; @observable ext2Visible = false;
@observable selectorVisible = false;
@observable f_name; @observable f_name;
@observable f_desc; @observable f_desc;
@ -47,6 +48,7 @@ class Store {
}; };
loadDeploys = (app_id) => { loadDeploys = (app_id) => {
this.records[`a${app_id}`].isLoaded = true;
return http.get('/api/app/deploy/', {params: {app_id}}) return http.get('/api/app/deploy/', {params: {app_id}})
.then(res => this.records[`a${app_id}`]['deploys'] = res) .then(res => this.records[`a${app_id}`]['deploys'] = res)
}; };