From 7fbd22772ffcb5cf3ff35c952c91ca645cab8977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E4=BA=8C=E7=8C=9B?= Date: Sat, 14 Dec 2019 00:37:27 +0800 Subject: [PATCH] A web update --- spug_web/src/menus.js | 5 + .../src/pages/config/environment/store.js | 8 +- spug_web/src/pages/deploy/app/AddSelect.js | 69 ++++++++ spug_web/src/pages/deploy/app/Ext1Form.js | 29 ++++ spug_web/src/pages/deploy/app/Ext2Form.js | 46 +++++ spug_web/src/pages/deploy/app/Setup1.js | 68 ++++++++ spug_web/src/pages/deploy/app/Setup2.js | 80 +++++++++ spug_web/src/pages/deploy/app/Setup3.js | 161 ++++++++++++++++++ spug_web/src/pages/deploy/app/Table.js | 77 +++++++++ spug_web/src/pages/deploy/app/index.js | 31 ++++ .../src/pages/deploy/app/index.module.css | 39 +++++ spug_web/src/pages/deploy/app/store.js | 58 +++++++ spug_web/src/pages/deploy/routes.js | 7 + spug_web/src/routes.js | 2 + 14 files changed, 679 insertions(+), 1 deletion(-) create mode 100644 spug_web/src/pages/deploy/app/AddSelect.js create mode 100644 spug_web/src/pages/deploy/app/Ext1Form.js create mode 100644 spug_web/src/pages/deploy/app/Ext2Form.js create mode 100644 spug_web/src/pages/deploy/app/Setup1.js create mode 100644 spug_web/src/pages/deploy/app/Setup2.js create mode 100644 spug_web/src/pages/deploy/app/Setup3.js create mode 100644 spug_web/src/pages/deploy/app/Table.js create mode 100644 spug_web/src/pages/deploy/app/index.js create mode 100644 spug_web/src/pages/deploy/app/index.module.css create mode 100644 spug_web/src/pages/deploy/app/store.js create mode 100644 spug_web/src/pages/deploy/routes.js 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