diff --git a/spug_web/src/menus.js b/spug_web/src/menus.js
index 6f97f13..6039b89 100644
--- a/spug_web/src/menus.js
+++ b/spug_web/src/menus.js
@@ -7,6 +7,11 @@ export default [
{title: '模板管理', path: '/exec/template'},
]
},
+ {
+ icon: 'flag', title: '应用发布', child: [
+ {title: '应用管理', path: '/deploy/app'},
+ ]
+ },
{icon: 'schedule', title: '任务计划', path: '/schedule'},
{
icon: 'deployment-unit', title: '配置中心', child: [
diff --git a/spug_web/src/pages/config/environment/store.js b/spug_web/src/pages/config/environment/store.js
index 946c022..679018f 100644
--- a/spug_web/src/pages/config/environment/store.js
+++ b/spug_web/src/pages/config/environment/store.js
@@ -4,6 +4,7 @@ import http from 'libs/http';
class Store {
@observable records = [];
@observable record = {};
+ @observable idMap = {};
@observable isFetching = false;
@observable formVisible = false;
@@ -12,7 +13,12 @@ class Store {
fetchRecords = () => {
this.isFetching = true;
return http.get('/api/config/environment/')
- .then(res => this.records = res)
+ .then(res => {
+ this.records = res;
+ for (let item of res) {
+ this.idMap[item.id] = item
+ }
+ })
.finally(() => this.isFetching = false)
};
diff --git a/spug_web/src/pages/deploy/app/AddSelect.js b/spug_web/src/pages/deploy/app/AddSelect.js
new file mode 100644
index 0000000..dbd5c1c
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/AddSelect.js
@@ -0,0 +1,69 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Modal, Card, Icon } from 'antd';
+import store from './store';
+import styles from './index.module.css';
+
+@observer
+class AddSelect extends React.Component {
+ switchExt1 = () => {
+ store.addVisible = false;
+ store.ext1Visible = true;
+ };
+
+ switchExt2 = () => {
+ store.addVisible = false;
+ store.ext2Visible = true;
+ };
+
+ render() {
+ const modalStyle = {
+ display: 'flex',
+ justifyContent: 'space-around',
+ backgroundColor: 'rgba(240, 242, 245, 1)',
+ padding: '80px 0'
+ };
+
+ return (
+ store.addVisible = false}
+ footer={null}>
+
+
+
+
+
+
常规发布
+
+ 由 Spug 来控制发布的主流程,你可以通过添加钩子脚本来执行额外的自定义操作。
+
+
+
+
+
+
+
+
+
自定义发布
+
+ 你可以完全自己定义发布的所有流程和操作,Spug 负责按顺序依次执行你记录的动作。
+
+
+
+
+ )
+ }
+}
+
+export default AddSelect
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/Ext1Form.js b/spug_web/src/pages/deploy/app/Ext1Form.js
new file mode 100644
index 0000000..6bf8c84
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/Ext1Form.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Modal, Steps } from 'antd';
+import Setup1 from './Setup1';
+import Setup2 from './Setup2';
+import Setup3 from './Setup3';
+import store from './store';
+import styles from './index.module.css';
+
+export default observer(function () {
+ return (
+ store.ext1Visible = false}
+ footer={null}>
+
+
+
+
+
+ {store.page === 0 && }
+ {store.page === 1 && }
+ {store.page === 2 && }
+
+ )
+})
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/Ext2Form.js b/spug_web/src/pages/deploy/app/Ext2Form.js
new file mode 100644
index 0000000..0c94433
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/Ext2Form.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Modal, message } from 'antd';
+import http from 'libs/http';
+import store from './store';
+
+@observer
+class ComForm extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false,
+ type: null,
+ body: store.record['body'],
+ }
+ }
+
+ handleSubmit = () => {
+ this.setState({loading: true});
+ const formData = this.props.form.getFieldsValue();
+ formData['id'] = store.record.id;
+ formData['body'] = this.state.body;
+ http.post('/api/exec/template/', formData)
+ .then(res => {
+ message.success('操作成功');
+ store.formVisible = false;
+ store.fetchRecords()
+ }, () => this.setState({loading: false}))
+ };
+
+ render() {
+ return (
+ store.ext2Visible = false}
+ footer={null}>
+
+
+ )
+ }
+}
+
+export default ComForm
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/Setup1.js b/spug_web/src/pages/deploy/app/Setup1.js
new file mode 100644
index 0000000..90f6776
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/Setup1.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Link } from 'react-router-dom';
+import { Switch, Col, Form, Input, Select, Button } from "antd";
+import envStore from 'pages/config/environment/store';
+import store from './store';
+
+@observer
+class Setup1 extends React.Component {
+ update = (key, value) => {
+ store.record[key] = value
+ };
+
+ render() {
+ const info = store.record;
+ const itemLayout = {
+ labelCol: {span: 6},
+ wrapperCol: {span: 14}
+ };
+ const itemTailLayout = {
+ labelCol: {span: 6},
+ wrapperCol: {span: 14, offset: 6}
+ };
+ return (
+
+ info.name = e.target.value} placeholder="请输入应用名称"/>
+
+
+
+
+
+
+ 新建环境
+
+
+
+ info['git_repo'] = e.target.value} placeholder="请输入Git仓库地址"/>
+
+
+
+
+
+ info['is_audit'] = v}/>
+
+
+
+
+
+ )
+ }
+}
+
+export default Form.create()(Setup1)
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/Setup2.js b/spug_web/src/pages/deploy/app/Setup2.js
new file mode 100644
index 0000000..5ac4f75
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/Setup2.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Form, Input, Select, Button, Icon } from "antd";
+import envStore from 'pages/config/environment/store';
+import store from './store';
+import hostStore from 'pages/host/store';
+import styles from './index.module.css';
+
+@observer
+class Setup1 extends React.Component {
+ componentDidMount() {
+ if (envStore.records.length === 0) {
+ envStore.fetchRecords()
+ }
+ if (hostStore.records.length === 0) {
+ hostStore.fetchRecords()
+ }
+ }
+
+ checkStatus = () => {
+ const info = store.record;
+ return info['dst_dir'] && info['dst_repo'] && info['versions'] && info['host_ids'].filter(x => x).length > 0
+ };
+
+ render() {
+ const info = store.record;
+ const itemLayout = {
+ labelCol: {span: 6},
+ wrapperCol: {span: 14}
+ };
+ const itemTailLayout = {
+ labelCol: {span: 6},
+ wrapperCol: {span: 14, offset: 6}
+ };
+ return (
+
+ info['dst_dir'] = e.target.value} placeholder="请输入目标主机部署路径"/>
+
+
+ info['dst_repo'] = e.target.value} placeholder="请输入目标主机仓库路径"/>
+
+
+ info['versions'] = e.target.value} placeholder="请输入保留历史版本数量"/>
+
+
+ {info['host_ids'].map((id, index) => (
+
+
+ {info['host_ids'].length > 1 && (
+ store.delHost(index)}/>
+ )}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+export default Setup1
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/Setup3.js b/spug_web/src/pages/deploy/app/Setup3.js
new file mode 100644
index 0000000..1ed3144
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/Setup3.js
@@ -0,0 +1,161 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Form, Row, Col, Button, Radio, Icon, message } from "antd";
+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 envStore from 'pages/config/environment/store';
+import store from './store';
+import http from 'libs/http';
+import hostStore from 'pages/host/store';
+import styles from './index.module.css';
+
+@observer
+class Setup1 extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loading: false
+ }
+ }
+
+ componentDidMount() {
+ if (envStore.records.length === 0) {
+ envStore.fetchRecords()
+ }
+ if (hostStore.records.length === 0) {
+ hostStore.fetchRecords()
+ }
+ }
+
+ handleSubmit = () => {
+ this.setState({loading: true});
+ const info = store.record;
+ info['extend'] = '1';
+ info['host_ids'] = info['host_ids'].filter(x => x);
+ http.post('/api/app/', info)
+ .then(() => {
+ message.success('保存成功');
+ store.fetchRecords();
+ store.ext1Visible = false
+ }, () => this.setState({loading: false}))
+ };
+
+ FilterLabel = (props) => (
+
+ 文件过滤:
+ store.record['filter_rule']['type'] = e.target.value}>
+ 包含
+ 排除
+
+
+ );
+
+ render() {
+ const info = store.record;
+ const itemTailLayout = {
+ labelCol: {span: 6},
+ wrapperCol: {span: 14, offset: 6}
+ };
+ return (
+
+
+
+ }>
+ info['filter_rule']['data'] = v}
+ style={{border: '1px solid #e8e8e8'}}/>
+
+
+ info['hook_pre_server'] = v}
+ style={{border: '1px solid #e8e8e8'}}/>
+
+
+ info['hook_pre_host'] = v}
+ style={{border: '1px solid #e8e8e8'}}/>
+
+
+
+
+
+ 基础设置
+
+
+
+ 检出代码
+
+
+
+ 版本切换
+
+
+
+
+ info['custom_envs'] = v}
+ style={{border: '1px solid #e8e8e8'}}/>
+
+
+ info['hook_post_server'] = v}
+ style={{border: '1px solid #e8e8e8'}}/>
+
+
+ info['hook_post_host'] = v}
+ style={{border: '1px solid #e8e8e8'}}/>
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+export default Setup1
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/Table.js b/spug_web/src/pages/deploy/app/Table.js
new file mode 100644
index 0000000..0a7251d
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/Table.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Table, Divider, Modal, Tag, Icon, message } from 'antd';
+import http from 'libs/http';
+import store from './store';
+import { LinkButton } from "components";
+import envStore from 'pages/config/environment/store';
+import lds from 'lodash';
+
+@observer
+class ComTable extends React.Component {
+ componentDidMount() {
+ store.fetchRecords();
+ if (envStore.records.length === 0) {
+ envStore.fetchRecords()
+ }
+ }
+
+ columns = [{
+ title: '序号',
+ key: 'series',
+ render: (_, __, index) => index + 1,
+ width: 80,
+ }, {
+ title: '应用名称',
+ dataIndex: 'name',
+ }, {
+ title: '模式',
+ dataIndex: 'extend',
+ render: value => value === '1' ? :
+
+ }, {
+ title: '发布环境',
+ dataIndex: 'env_id',
+ render: value => lds.get(envStore.idMap, `${value}.name`)
+ }, {
+ title: '发布审核',
+ dataIndex: 'is_audit',
+ render: value => value ? 开启 : 关闭
+ }, {
+ title: '操作',
+ render: info => (
+
+ store.showForm(info)}>编辑
+
+ this.handleDelete(info)}>删除
+
+ )
+ }];
+
+ handleDelete = (text) => {
+ Modal.confirm({
+ title: '删除确认',
+ content: `确定要删除【${text['name']}】?`,
+ onOk: () => {
+ return http.delete('/api/exec/template/', {params: {id: text.id}})
+ .then(() => {
+ message.success('删除成功');
+ store.fetchRecords()
+ })
+ }
+ })
+ };
+
+ render() {
+ console.debug(JSON.stringify(envStore.idMap));
+ let data = store.records;
+ if (store.f_name) {
+ data = data.filter(item => item['name'].toLowerCase().includes(store.f_name.toLowerCase()))
+ }
+ return (
+
+ )
+ }
+}
+
+export default ComTable
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/index.js b/spug_web/src/pages/deploy/app/index.js
new file mode 100644
index 0000000..391ec4e
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/index.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import { observer } from 'mobx-react';
+import { Card, Input, Button } from 'antd';
+import { SearchForm } from 'components';
+import ComTable from './Table';
+import Ext1Form from './Ext1Form';
+import Ext2Form from './Ext2Form';
+import AddSelect from './AddSelect';
+import store from './store';
+
+export default observer(function () {
+ return (
+
+
+
+ store.f_name = e.target.value} placeholder="请输入"/>
+
+
+
+
+
+
+
+
+
+ {store.addVisible && }
+ {store.ext1Visible && }
+ {store.ext2Visible && }
+
+ )
+})
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/index.module.css b/spug_web/src/pages/deploy/app/index.module.css
new file mode 100644
index 0000000..2d01ce9
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/index.module.css
@@ -0,0 +1,39 @@
+.steps {
+ width: 520px;
+ margin: 0 auto 30px;
+}
+
+.delIcon {
+ font-size: 24px;
+ position: relative;
+ top: 4px
+}
+
+.deployBlock {
+ height: 100px;
+ margin-top: 63px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.cardBlock {
+ display: flex;
+ justify-content: space-around;
+ background-color: rgba(240, 242, 245, 1);
+ padding: 50px 0;
+}
+
+.cardTitle {
+ margin-bottom: 12px;
+ font-weight: 500;
+ font-size: 16px;
+ color: rgba(0, 0, 0, .85);
+}
+
+.cardDesc {
+ height: 64px;
+ overflow: hidden;
+ color: rgba(0, 0, 0, .65);
+}
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/app/store.js b/spug_web/src/pages/deploy/app/store.js
new file mode 100644
index 0000000..3ac9366
--- /dev/null
+++ b/spug_web/src/pages/deploy/app/store.js
@@ -0,0 +1,58 @@
+import { observable } from "mobx";
+import http from 'libs/http';
+
+class Store {
+ @observable records = [];
+ @observable record = {};
+ @observable page = 0;
+ @observable isFetching = false;
+ @observable addVisible = false;
+ @observable ext1Visible = false;
+ @observable ext2Visible = false;
+
+ @observable f_name;
+
+ initRecord = () => ({
+ git_type: 'branch',
+ is_audit: false,
+ versions: 10,
+ host_ids: [undefined],
+ filter_rule: {type: 'contain', data: ''}
+ });
+
+ fetchRecords = () => {
+ this.isFetching = true;
+ http.get('/api/app/')
+ .then(res => this.records = res)
+ .finally(() => this.isFetching = false)
+ };
+
+ showForm = (info) => {
+ this.page = 0;
+ if (info) {
+ if (info.extend === '1') {
+ this.ext1Visible = true
+ } else {
+ this.ext2Visible = true
+ }
+ this.record = info
+ } else {
+ this.addVisible = true;
+ this.record = this.initRecord()
+ }
+ };
+
+ addHost = () => {
+ this.record['host_ids'].push(undefined)
+ };
+
+ editHost = (index, v) => {
+ this.record['host_ids'][index] = v
+ };
+
+ delHost = (index) => {
+ this.record['host_ids'].splice(index, 1)
+ }
+}
+
+export default new Store()
\ No newline at end of file
diff --git a/spug_web/src/pages/deploy/routes.js b/spug_web/src/pages/deploy/routes.js
new file mode 100644
index 0000000..4a4547b
--- /dev/null
+++ b/spug_web/src/pages/deploy/routes.js
@@ -0,0 +1,7 @@
+import { makeRoute } from "../../libs/router";
+import app from './app';
+
+
+export default [
+ makeRoute('/app', app),
+]
\ No newline at end of file
diff --git a/spug_web/src/routes.js b/spug_web/src/routes.js
index 18150f6..916d48e 100644
--- a/spug_web/src/routes.js
+++ b/spug_web/src/routes.js
@@ -8,6 +8,7 @@ import scheduleRoutes from './pages/schedule/routes';
import monitorRoutes from './pages/monitor/routes';
import alarmRoutes from './pages/alarm/routes';
import configRoutes from './pages/config/routes';
+import deployRoutes from './pages/deploy/routes';
export default [
@@ -19,4 +20,5 @@ export default [
makeModuleRoute('/monitor', monitorRoutes),
makeModuleRoute('/alarm', alarmRoutes),
makeModuleRoute('/config', configRoutes),
+ makeModuleRoute('/deploy', deployRoutes),
]
\ No newline at end of file