mirror of https://github.com/openspug/spug
upgrade deploy
parent
d076c51c3d
commit
4d86288ab5
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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: ''}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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/>}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>
|
||||||
};
|
文件过滤规则
|
||||||
|
<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
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue