U 发布配置克隆已支持跨应用克隆并新增查看配置功能

pull/161/head
vapao 2020-07-29 01:39:24 +08:00
parent 488bc3d7bf
commit d539544fa6
11 changed files with 182 additions and 56 deletions

View File

@ -0,0 +1,60 @@
/**
* 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 { Cascader, Form } from 'antd';
import envStore from 'pages/config/environment/store';
import store from './store';
import lds from 'lodash';
import { toJS } from "mobx";
@observer
class CloneConfirm extends React.Component {
handleLoadData = (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
if (targetOption.deploys === undefined) {
targetOption.loading = true;
store.loadDeploys(targetOption.value).then(() => targetOption.loading = false)
}
}
handleData = records => {
return records.map(x => {
const option = {
label: x.name,
value: x.id,
deploys: x.deploys,
isLeaf: false
}
if (x.children) {
option.children = x.children
} else if (x.deploys) {
option.children = x.deploys.map(item => ({
label: lds.get(envStore.idMap, `${item.env_id}.name`),
value: JSON.stringify(item),
id: `${x.id},${item.env_id}`,
}))
}
return option
})
}
render() {
const options = this.handleData(Object.values(toJS(store.records)));
return (
<Form>
<Form.Item required label="应用及环境" help="克隆配置,将基于选择对象的配置来创建新的发布配置。">
<Cascader
options={options}
loadData={this.handleLoadData}
onChange={this.props.onChange}/>
</Form.Item>
</Form>
)
}
}
export default CloneConfirm

View File

@ -13,12 +13,19 @@ import store from './store';
import styles from './index.module.css';
export default observer(function Ext1From() {
const appName = store.records[store.app_id].name;
let title = `常规发布 - ${appName}`;
if (store.deploy.id) {
store.isReadOnly ? title = '查看' + title : title = '编辑' + title;
} else {
title = '新建' + title
}
return (
<Modal
visible
width={900}
maskClosable={false}
title={store.deploy.id ? '编辑常规发布' : '新建常规发布'}
title={title}
onCancel={() => store.ext1Visible = false}
footer={null}>
<Steps current={store.page} className={styles.steps}>

View File

@ -16,21 +16,23 @@ export default observer(function Ext2Setup1() {
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
<Form.Item required label="发布环境">
<Col span={16}>
<Select value={info.env_id} onChange={v => info.env_id = v} placeholder="请选择发布环境">
<Select disabled={store.isReadOnly} value={info.env_id} onChange={v => info.env_id = v} placeholder="请选择发布环境">
{envStore.records.map(item => (
<Select.Option value={item.id} key={item.id}>{item.name}</Select.Option>
))}
</Select>
</Col>
<Col span={6} offset={2}>
<Link to="/config/environment">新建环境</Link>
<Link disabled={store.isReadOnly} to="/config/environment">新建环境</Link>
</Col>
</Form.Item>
<Form.Item required label="Git仓库地址">
<Input value={info['git_repo']} onChange={e => info['git_repo'] = e.target.value} placeholder="请输入Git仓库地址"/>
<Input disabled={store.isReadOnly} value={info['git_repo']} onChange={e => info['git_repo'] = e.target.value}
placeholder="请输入Git仓库地址"/>
</Form.Item>
<Form.Item label="发布审核">
<Switch
disabled={store.isReadOnly}
checkedChildren="开启"
unCheckedChildren="关闭"
checked={info['is_audit']}
@ -42,7 +44,7 @@ export default observer(function Ext2Setup1() {
href="https://spug.dev/docs/install-error/#%E9%92%89%E9%92%89%E6%94%B6%E4%B8%8D%E5%88%B0%E9%80%9A%E7%9F%A5%EF%BC%9F">钉钉收不到通知</a>
</span>}>
<Input addonBefore={(
<Select
<Select disabled={store.isReadOnly}
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>
<Select.Option value="0">关闭</Select.Option>
<Select.Option value="1">钉钉</Select.Option>
@ -50,7 +52,7 @@ export default observer(function Ext2Setup1() {
<Select.Option value="2">Webhook</Select.Option>
</Select>
)}
disabled={info['rst_notify']['mode'] === '0'}
disabled={store.isReadOnly || info['rst_notify']['mode'] === '0'}
value={info['rst_notify']['value']}
onChange={e => info['rst_notify']['value'] = e.target.value}
placeholder="请输入"/>

View File

@ -37,13 +37,14 @@ class Ext1Setup2 extends React.Component {
return (
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
<Form.Item required label="目标主机部署路径" help="目标主机的应用根目录,例如:/var/www/html">
<Input value={info['dst_dir']} onChange={e => info['dst_dir'] = e.target.value} placeholder="请输入目标主机部署路径"/>
<Input disabled={store.isReadOnly} value={info['dst_dir']} onChange={e => info['dst_dir'] = e.target.value}
placeholder="请输入目标主机部署路径"/>
</Form.Item>
<Form.Item required label="目标主机仓库路径" help="此目录用于存储应用的历史版本,例如:/data/spug/repos">
<Input value={info['dst_repo']} onChange={e => info['dst_repo'] = e.target.value} placeholder="请输入目标主机仓库路径"/>
<Input disabled={store.isReadOnly} value={info['dst_repo']} onChange={e => info['dst_repo'] = e.target.value} placeholder="请输入目标主机仓库路径"/>
</Form.Item>
<Form.Item required label="保留历史版本数量" help="早于指定数量的历史版本会被删除,以释放空间">
<Input value={info['versions']} onChange={e => info['versions'] = e.target.value} placeholder="请输入保留历史版本数量"/>
<Input disabled={store.isReadOnly} value={info['versions']} onChange={e => info['versions'] = e.target.value} placeholder="请输入保留历史版本数量"/>
</Form.Item>
<Form.Item required label="发布目标主机">
{info['host_ids'].map((id, index) => (
@ -52,6 +53,7 @@ class Ext1Setup2 extends React.Component {
value={id}
showSearch
placeholder="请选择"
disabled={store.isReadOnly}
style={{width: '80%', marginRight: 10}}
optionFilterProp="children"
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
@ -62,14 +64,14 @@ class Ext1Setup2 extends React.Component {
</Select.Option>
))}
</Select>
{info['host_ids'].length > 1 && (
{!store.isReadOnly && info['host_ids'].length > 1 && (
<Icon className={styles.delIcon} type="minus-circle-o" onClick={() => store.delHost(index)}/>
)}
</React.Fragment>
))}
</Form.Item>
<Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button type="dashed" style={{width: '80%'}} onClick={store.addHost}>
<Button disabled={store.isReadOnly} type="dashed" style={{width: '80%'}} onClick={store.addHost}>
<Icon type="plus"/>添加目标主机
</Button>
</Form.Item>

View File

@ -55,6 +55,7 @@ class Ext1Setup3 extends React.Component {
<div style={{display: 'inline-block', height: 39, width: 390}}>
<span style={{float: 'left'}}>文件过滤<span style={{margin: '0 8px 0 2px'}}>:</span></span>
<Radio.Group
disabled={store.isReadOnly}
style={{marginLeft: 20, float: 'left'}}
value={props.type}
onChange={e => store.deploy['filter_rule']['type'] = e.target.value}>
@ -105,6 +106,7 @@ class Ext1Setup3 extends React.Component {
className={full === '1' ? styles.fullScreen : null}
label={<this.FilterLabel type={info['filter_rule']['type']}/>}>
<Editor
readOnly={store.isReadOnly}
mode="text"
theme="tomorrow"
width="100%"
@ -119,6 +121,7 @@ class Ext1Setup3 extends React.Component {
className={full === '3' ? styles.fullScreen : null}
label={<this.NormalLabel title="代码检出前执行" id="3"/>}>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
@ -133,6 +136,7 @@ class Ext1Setup3 extends React.Component {
className={full === '5' ? styles.fullScreen : null}
label={<this.NormalLabel title="应用发布前执行" id="5"/>}>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
@ -163,6 +167,7 @@ class Ext1Setup3 extends React.Component {
className={full === '2' ? styles.fullScreen : null}
label={<this.NormalLabel title="自定义全局变量" id="2"/>}>
<Editor
readOnly={store.isReadOnly}
mode="text"
theme="tomorrow"
width="100%"
@ -177,6 +182,7 @@ class Ext1Setup3 extends React.Component {
className={full === '4' ? styles.fullScreen : null}
label={<this.NormalLabel title="代码检出后执行" id="4"/>}>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
@ -191,6 +197,7 @@ class Ext1Setup3 extends React.Component {
className={full === '6' ? styles.fullScreen : null}
label={<this.NormalLabel title="应用发布后执行" id="6"/>}>
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
@ -203,7 +210,7 @@ class Ext1Setup3 extends React.Component {
</Col>
</Row>
<Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button type="primary" onClick={this.handleSubmit}>提交</Button>
<Button disabled={store.isReadOnly} type="primary" onClick={this.handleSubmit}>提交</Button>
<Button style={{marginLeft: 20}} onClick={() => store.page -= 1}>上一步</Button>
</Form.Item>
</React.Fragment>

View File

@ -13,12 +13,19 @@ import Setup3 from './Ext2Setup3';
import store from './store';
export default observer(function Ext2From() {
const appName = store.records[store.app_id].name;
let title = `自定义发布 - ${appName}`;
if (store.deploy.id) {
store.isReadOnly ? title = '查看' + title : title = '编辑' + title;
} else {
title = '新建' + title
}
return (
<Modal
visible
width={900}
maskClosable={false}
title={store.deploy.id ? '编辑自定义发布' : '新建自定义发布'}
title={title}
onCancel={() => store.ext2Visible = false}
footer={null}>
<Steps current={store.page} className={styles.steps}>

View File

@ -16,18 +16,19 @@ export default observer(function Ext2Setup1() {
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
<Form.Item required label="发布环境">
<Col span={16}>
<Select value={info.env_id} onChange={v => info.env_id = v} placeholder="请选择发布环境">
<Select disabled={store.isReadOnly} value={info.env_id} onChange={v => info.env_id = v} placeholder="请选择发布环境">
{envStore.records.map(item => (
<Select.Option value={item.id} key={item.id}>{item.name}</Select.Option>
))}
</Select>
</Col>
<Col span={6} offset={2}>
<Link to="/config/environment">新建环境</Link>
<Link disabled={store.isReadOnly} to="/config/environment">新建环境</Link>
</Col>
</Form.Item>
<Form.Item label="发布审核">
<Switch
disabled={store.isReadOnly}
checkedChildren="开启"
unCheckedChildren="关闭"
checked={info['is_audit']}
@ -39,7 +40,7 @@ export default observer(function Ext2Setup1() {
href="https://spug.dev/docs/install-error/#%E9%92%89%E9%92%89%E6%94%B6%E4%B8%8D%E5%88%B0%E9%80%9A%E7%9F%A5%EF%BC%9F">钉钉收不到通知</a>
</span>}>
<Input addonBefore={(
<Select
<Select disabled={store.isReadOnly}
value={info['rst_notify']['mode']} style={{width: 100}} onChange={v => info['rst_notify']['mode'] = v}>
<Select.Option value="0">关闭</Select.Option>
<Select.Option value="1">钉钉</Select.Option>
@ -47,7 +48,7 @@ export default observer(function Ext2Setup1() {
<Select.Option value="2">Webhook</Select.Option>
</Select>
)}
disabled={info['rst_notify']['mode'] === '0'}
disabled={store.isReadOnly || info['rst_notify']['mode'] === '0'}
value={info['rst_notify']['value']}
onChange={e => info['rst_notify']['value'] = e.target.value}
placeholder="请输入"/>

View File

@ -28,6 +28,7 @@ class Ext2Setup2 extends React.Component {
<Select
value={id}
showSearch
disabled={store.isReadOnly}
placeholder="请选择"
optionFilterProp="children"
style={{width: '80%', marginRight: 10}}
@ -39,14 +40,14 @@ class Ext2Setup2 extends React.Component {
</Select.Option>
))}
</Select>
{info['host_ids'].length > 1 && (
{!store.isReadOnly && info['host_ids'].length > 1 && (
<Icon className={styles.delIcon} type="minus-circle-o" onClick={() => store.delHost(index)}/>
)}
</React.Fragment>
))}
</Form.Item>
<Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button type="dashed" style={{width: '80%'}} onClick={store.addHost}>
<Button disabled={store.isReadOnly} type="dashed" style={{width: '80%'}} onClick={store.addHost}>
<Icon type="plus"/>添加目标主机
</Button>
</Form.Item>

View File

@ -64,11 +64,13 @@ class Ext2Setup3 extends React.Component {
{server_actions.map((item, index) => (
<div key={index} style={{marginBottom: 30, position: 'relative'}}>
<Form.Item required label={`本地动作${index + 1}`}>
<Input value={item['title']} onChange={e => item['title'] = e.target.value} placeholder="请输入"/>
<Input disabled={store.isReadOnly} value={item['title']} onChange={e => item['title'] = e.target.value}
placeholder="请输入"/>
</Form.Item>
<Form.Item required label="执行内容">
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
@ -77,21 +79,26 @@ class Ext2Setup3 extends React.Component {
onChange={v => item['data'] = cleanCommand(v)}
placeholder="请输入要执行的动作"/>
</Form.Item>
{!store.isReadOnly && (
<div className={styles.delAction} onClick={() => server_actions.splice(index, 1)}>
<Icon type="minus-circle"/>移除
</div>
)}
</div>
))}
{!store.isReadOnly && (
<Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button type="dashed" block onClick={() => server_actions.push({})}>
<Icon type="plus"/>添加本地执行动作在服务端本地执行
</Button>
</Form.Item>
)}
<Divider/>
{host_actions.map((item, index) => (
<div key={index} style={{marginBottom: 30, position: 'relative'}}>
<Form.Item required label={`目标主机动作${index + 1}`}>
<Input value={item['title']} onChange={e => item['title'] = e.target.value} placeholder="请输入"/>
<Input disabled={store.isReadOnly} value={item['title']} onChange={e => item['title'] = e.target.value}
placeholder="请输入"/>
</Form.Item>
{item['type'] === 'transfer' ? ([
<Form.Item key={0} label="过滤规则" help={this.helpMap[item['mode']]}>
@ -100,9 +107,10 @@ class Ext2Setup3 extends React.Component {
placeholder="请输入逗号分割的过滤规则"
value={item['rule']}
onChange={e => item['rule'] = e.target.value.replace('', ',')}
disabled={item['mode'] === '0'}
disabled={store.isReadOnly || item['mode'] === '0'}
addonBefore={(
<Select style={{width: 100}} value={item['mode']} onChange={v => item['mode'] = v}>
<Select disabled={store.isReadOnly} style={{width: 100}} value={item['mode']}
onChange={v => item['mode'] = v}>
<Select.Option value="0">关闭</Select.Option>
<Select.Option value="1">包含</Select.Option>
<Select.Option value="2">排除</Select.Option>
@ -113,11 +121,13 @@ class Ext2Setup3 extends React.Component {
target="_blank" rel="noopener noreferrer"
href="https://spug.dev/docs/deploy-config#%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93">使用前请务必阅读官方文档</a>}>
<Input
disabled={store.isReadOnly}
spellCheck={false}
value={item['src']}
placeholder="请输入本地路径部署spug的容器或主机"
onChange={e => item['src'] = e.target.value}/>
<Input
disabled={store.isReadOnly}
spellCheck={false}
value={item['dst']}
placeholder="请输入目标主机路径"
@ -126,6 +136,7 @@ class Ext2Setup3 extends React.Component {
]) : (
<Form.Item required label="执行内容">
<Editor
readOnly={store.isReadOnly}
mode="sh"
theme="tomorrow"
width="100%"
@ -135,27 +146,31 @@ class Ext2Setup3 extends React.Component {
placeholder="请输入要执行的动作"/>
</Form.Item>
)}
{!store.isReadOnly && (
<div className={styles.delAction} onClick={() => host_actions.splice(index, 1)}>
<Icon type="minus-circle"/>移除
</div>
)}
</div>
))}
{!store.isReadOnly && (
<Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button type="dashed" block onClick={() => host_actions.push({})}>
<Button disabled={store.isReadOnly} type="dashed" block onClick={() => host_actions.push({})}>
<Icon type="plus"/>添加目标主机执行动作在部署目标主机执行
</Button>
<Button
block
type="dashed"
disabled={lds.findIndex(host_actions, x => x.type === 'transfer') !== -1}
disabled={store.isReadOnly || lds.findIndex(host_actions, x => x.type === 'transfer') !== -1}
onClick={() => host_actions.push({type: 'transfer', title: '数据传输', mode: '0'})}>
<Icon type="plus"/>添加数据传输动作仅能添加一个
</Button>
</Form.Item>
)}
<Form.Item wrapperCol={{span: 14, offset: 6}}>
<Button
type="primary"
disabled={[...host_actions, ...server_actions].filter(x => x.title && x.data).length === 0}
disabled={store.isReadOnly || [...host_actions, ...server_actions].filter(x => x.title && x.data).length === 0}
loading={this.state.loading}
onClick={this.handleSubmit}>提交</Button>
<Button style={{marginLeft: 20}} onClick={() => store.page -= 1}>上一步</Button>

View File

@ -10,11 +10,17 @@ import { Table, Divider, Modal, Tag, Icon, message } from 'antd';
import http from 'libs/http';
import store from './store';
import { LinkButton } from "components";
import CloneConfirm from './CloneConfirm';
import envStore from 'pages/config/environment/store';
import lds from 'lodash';
@observer
class ComTable extends React.Component {
constructor(props) {
super(props);
this.cloneObj = {};
}
componentDidMount() {
store.fetchRecords();
if (envStore.records.length === 0) {
@ -43,6 +49,8 @@ class ComTable extends React.Component {
<span>
<LinkButton auth="deploy.app.edit" onClick={e => store.showExtForm(e, info.id)}>新建发布</LinkButton>
<Divider type="vertical"/>
<LinkButton auth="deploy.app.edit" onClick={e => this.handleClone(e, info.id)}>克隆发布</LinkButton>
<Divider type="vertical"/>
<LinkButton auth="deploy.app.edit" onClick={e => store.showForm(e, info)}>编辑</LinkButton>
<Divider type="vertical"/>
<LinkButton auth="deploy.app.del" onClick={e => this.handleDelete(e, info)}>删除</LinkButton>
@ -50,6 +58,19 @@ class ComTable extends React.Component {
)
}];
handleClone = (e, id) => {
e.stopPropagation();
Modal.confirm({
icon: 'exclamation-circle',
title: '选择克隆对象',
content: <CloneConfirm onChange={v => this.cloneObj = v[1]}/>,
onOk: () => {
const info = JSON.parse(this.cloneObj);
store.showExtForm(null, id, info, true)
},
})
};
handleDelete = (e, text) => {
e.stopPropagation();
Modal.confirm({
@ -102,9 +123,10 @@ class ComTable extends React.Component {
title: '操作',
render: info => (
<span>
<LinkButton auth="deploy.app.edit" onClick={e => store.showExtForm(e, record.id, info)}>编辑</LinkButton>
<LinkButton auth="deploy.app.edit"
onClick={e => store.showExtForm(e, record.id, info, false, true)}>查看</LinkButton>
<Divider type="vertical"/>
<LinkButton auth="deploy.app.edit" onClick={e => store.showExtForm(e, record.id, info, true)}>克隆配置</LinkButton>
<LinkButton auth="deploy.app.edit" onClick={e => store.showExtForm(e, record.id, info)}>编辑</LinkButton>
<Divider type="vertical"/>
<LinkButton auth="deploy.app.edit" onClick={() => this.handleDeployDelete(info)}>删除</LinkButton>
</span>

View File

@ -12,6 +12,7 @@ class Store {
@observable deploy = {};
@observable page = 0;
@observable loading = {};
@observable isReadOnly = false;
@observable isFetching = false;
@observable formVisible = false;
@observable addVisible = false;
@ -35,7 +36,7 @@ class Store {
};
loadDeploys = (app_id) => {
http.get('/api/app/deploy/', {params: {app_id}})
return http.get('/api/app/deploy/', {params: {app_id}})
.then(res => this.records[app_id]['deploys'] = res)
};
@ -45,10 +46,11 @@ class Store {
this.formVisible = true;
};
showExtForm = (e, app_id, info, isClone) => {
showExtForm = (e, app_id, info, isClone, isReadOnly = false) => {
if (e) e.stopPropagation();
this.page = 0;
this.app_id = app_id;
this.isReadOnly = isReadOnly
if (info) {
if (info.extend === '1') {
this.ext1Visible = true