mirror of https://github.com/openspug/spug
fix issue
parent
0dd2ef3df8
commit
083452dd90
|
@ -1,182 +0,0 @@
|
|||
/**
|
||||
* 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 { CaretRightOutlined, LoadingOutlined, PlayCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { Steps, Collapse, PageHeader, Spin, Tag, Button } from 'antd';
|
||||
import { http, history, X_TOKEN } from 'libs';
|
||||
import { AuthDiv } from 'components';
|
||||
import OutView from './OutView';
|
||||
import styles from './index.module.css';
|
||||
import store from './store';
|
||||
import lds from 'lodash';
|
||||
|
||||
@observer
|
||||
class Ext1Index extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.timer = null;
|
||||
this.id = props.match.params.id;
|
||||
this.log = props.match.params.log;
|
||||
this.state = {
|
||||
fetching: true,
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.socket) this.socket.close();
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
store.request = {targets: [], host_actions: [], server_actions: []};
|
||||
store.outputs = {};
|
||||
}
|
||||
|
||||
fetch = () => {
|
||||
if (!this.timer) this.setState({fetching: true});
|
||||
http.get(`/api/deploy/request/${this.id}/`, {params: {log: this.log}})
|
||||
.then(res => {
|
||||
store.request = res;
|
||||
const outputs = {}
|
||||
while (res.outputs.length) {
|
||||
const msg = JSON.parse(res.outputs.pop());
|
||||
if (!outputs.hasOwnProperty(msg.key)) {
|
||||
const data = msg.key === 'local' ? ['读取数据... '] : [];
|
||||
outputs[msg.key] = {data}
|
||||
}
|
||||
this._parse_message(msg, outputs)
|
||||
}
|
||||
store.outputs = outputs;
|
||||
if (store.request.status === '2') {
|
||||
this.timer = setTimeout(this.fetch, 2000)
|
||||
} else {
|
||||
this.timer = null
|
||||
}
|
||||
})
|
||||
.finally(() => this.setState({fetching: false}))
|
||||
};
|
||||
|
||||
_parse_message = (message, outputs) => {
|
||||
outputs = outputs || store.outputs;
|
||||
const {key, data, step, status} = message;
|
||||
if (data !== undefined) {
|
||||
outputs[key]['data'].push(data);
|
||||
}
|
||||
if (step !== undefined) outputs[key]['step'] = step;
|
||||
if (status !== undefined) outputs[key]['status'] = status;
|
||||
};
|
||||
|
||||
handleDeploy = () => {
|
||||
this.setState({loading: true});
|
||||
http.post(`/api/deploy/request/${this.id}/`)
|
||||
.then(({token, outputs}) => {
|
||||
store.request.status = '2';
|
||||
store.outputs = outputs;
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${token}/?x-token=${X_TOKEN}`);
|
||||
this.socket.onopen = () => {
|
||||
this.socket.send('ok');
|
||||
};
|
||||
this.socket.onmessage = e => {
|
||||
if (e.data === 'pong') {
|
||||
this.socket.send('ping')
|
||||
} else {
|
||||
this._parse_message(JSON.parse(e.data))
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => this.setState({loading: false}))
|
||||
};
|
||||
|
||||
getStatus = (key, n) => {
|
||||
const step = lds.get(store.outputs, `${key}.step`, -1);
|
||||
const isError = lds.get(store.outputs, `${key}.status`) === 'error';
|
||||
const icon = <LoadingOutlined />;
|
||||
if (n > step) {
|
||||
return {key: n, status: 'wait'}
|
||||
} else if (n === step) {
|
||||
return isError ? {key: n, status: 'error'} : {key: n, status: 'process', icon}
|
||||
} else {
|
||||
return {key: n, status: 'finish'}
|
||||
}
|
||||
};
|
||||
|
||||
getStatusAlias = () => {
|
||||
if (Object.keys(store.outputs).length !== 0) {
|
||||
for (let item of [{id: 'local'}, ...store.request.targets]) {
|
||||
if (lds.get(store.outputs, `${item.id}.status`) === 'error') {
|
||||
return <Tag color="red">发布异常</Tag>
|
||||
} else if (lds.get(store.outputs, `${item.id}.step`, -1) < 5) {
|
||||
return <Tag color="orange">发布中</Tag>
|
||||
}
|
||||
}
|
||||
return <Tag color="green">发布成功</Tag>
|
||||
} else {
|
||||
return <Tag color="blue">{store.request['status_alias'] || '...'}</Tag>
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {app_name, env_name, status} = store.request;
|
||||
return (
|
||||
<AuthDiv auth="deploy.request.do">
|
||||
<Spin spinning={this.state.fetching}>
|
||||
<PageHeader
|
||||
title="应用发布"
|
||||
subTitle={`${app_name} - ${env_name}`}
|
||||
style={{padding: 0}}
|
||||
tags={this.getStatusAlias()}
|
||||
extra={this.log ? (
|
||||
<Button icon={<SyncOutlined />} type="primary" onClick={this.fetch}>刷新</Button>
|
||||
) : (
|
||||
<Button icon={<PlayCircleOutlined />} loading={this.state.loading} type="primary"
|
||||
disabled={!['1', '-3'].includes(status)}
|
||||
onClick={this.handleDeploy}>发布</Button>
|
||||
)}
|
||||
onBack={() => history.goBack()}/>
|
||||
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
||||
<Collapse.Panel showArrow={false} key={1} header={
|
||||
<Steps>
|
||||
<Steps.Step {...this.getStatus('local', 0)} title="建立连接"/>
|
||||
<Steps.Step {...this.getStatus('local', 1)} title="发布准备"/>
|
||||
<Steps.Step {...this.getStatus('local', 2)} title="检出前任务"/>
|
||||
<Steps.Step {...this.getStatus('local', 3)} title="执行检出"/>
|
||||
<Steps.Step {...this.getStatus('local', 4)} title="检出后任务"/>
|
||||
<Steps.Step {...this.getStatus('local', 5)} title="执行打包"/>
|
||||
</Steps>}>
|
||||
<OutView id="local"/>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
||||
<Collapse
|
||||
defaultActiveKey={'0'}
|
||||
className={styles.collapse}
|
||||
expandIcon={({isActive}) => <CaretRightOutlined style={{fontSize: 16}} rotate={isActive ? 90 : 0} />}>
|
||||
{store.request.targets.map((item, index) => (
|
||||
<Collapse.Panel key={index} header={
|
||||
<div style={{display: 'flex', justifyContent: 'space-between'}}>
|
||||
<b>{item.title}</b>
|
||||
<Steps size="small" style={{maxWidth: 600}}>
|
||||
<Steps.Step {...this.getStatus(item.id, 1)} title="数据准备"/>
|
||||
<Steps.Step {...this.getStatus(item.id, 2)} title="发布前任务"/>
|
||||
<Steps.Step {...this.getStatus(item.id, 3)} title="执行发布"/>
|
||||
<Steps.Step {...this.getStatus(item.id, 4)} title="发布后任务"/>
|
||||
</Steps>
|
||||
</div>}>
|
||||
<OutView id={item.id}/>
|
||||
</Collapse.Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
</Spin>
|
||||
</AuthDiv>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Ext1Index
|
|
@ -1,188 +0,0 @@
|
|||
/**
|
||||
* 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 { CaretRightOutlined, LoadingOutlined, PlayCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { Steps, Collapse, PageHeader, Spin, Tag, Button } from 'antd';
|
||||
import { http, history, X_TOKEN } from 'libs';
|
||||
import { AuthDiv } from 'components';
|
||||
import OutView from './OutView';
|
||||
import styles from './index.module.css';
|
||||
import store from './store';
|
||||
import lds from 'lodash';
|
||||
|
||||
@observer
|
||||
class Ext1Index extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.timer = null;
|
||||
this.id = props.match.params.id;
|
||||
this.log = props.match.params.log;
|
||||
this.state = {
|
||||
fetching: true,
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.socket) this.socket.close();
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
store.request = {targets: [], server_actions: [], host_actions: []};
|
||||
store.outputs = {};
|
||||
}
|
||||
|
||||
|
||||
fetch = () => {
|
||||
if (!this.timer) this.setState({fetching: true});
|
||||
http.get(`/api/deploy/request/${this.id}/`, {params: {log: this.log}})
|
||||
.then(res => {
|
||||
store.request = res;
|
||||
const outputs = {};
|
||||
while (res.outputs.length) {
|
||||
const msg = JSON.parse(res.outputs.pop());
|
||||
if (!outputs.hasOwnProperty(msg.key)) {
|
||||
const data = msg.key === 'local' ? ['读取数据... '] : [];
|
||||
outputs[msg.key] = {data}
|
||||
}
|
||||
this._parse_message(msg, outputs)
|
||||
}
|
||||
store.outputs = outputs;
|
||||
if (store.request.status === '2') {
|
||||
this.timer = setTimeout(this.fetch, 2000)
|
||||
} else {
|
||||
this.timer = null
|
||||
}
|
||||
})
|
||||
.finally(() => this.setState({fetching: false}))
|
||||
};
|
||||
|
||||
_parse_message = (message, outputs) => {
|
||||
outputs = outputs || store.outputs;
|
||||
const {key, data, step, status} = message;
|
||||
if (data !== undefined) {
|
||||
outputs[key]['data'].push(data);
|
||||
}
|
||||
if (step !== undefined) outputs[key]['step'] = step;
|
||||
if (status !== undefined) outputs[key]['status'] = status;
|
||||
};
|
||||
|
||||
handleDeploy = () => {
|
||||
this.setState({loading: true});
|
||||
http.post(`/api/deploy/request/${this.id}/`)
|
||||
.then(({token, outputs}) => {
|
||||
store.request.status = '2';
|
||||
store.outputs = outputs;
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${token}/?x-token=${X_TOKEN}`);
|
||||
this.socket.onopen = () => {
|
||||
this.socket.send('ok');
|
||||
};
|
||||
this.socket.onmessage = e => {
|
||||
if (e.data === 'pong') {
|
||||
this.socket.send('ping')
|
||||
} else {
|
||||
this._parse_message(JSON.parse(e.data))
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => this.setState({loading: false}))
|
||||
};
|
||||
|
||||
getStatus = (key, n) => {
|
||||
const step = lds.get(store.outputs, `${key}.step`, -1);
|
||||
const isError = lds.get(store.outputs, `${key}.status`) === 'error';
|
||||
const icon = <LoadingOutlined />;
|
||||
if (n > step) {
|
||||
return {key: n, status: 'wait'}
|
||||
} else if (n === step) {
|
||||
return isError ? {key: n, status: 'error'} : {key: n, status: 'process', icon}
|
||||
} else {
|
||||
return {key: n, status: 'finish'}
|
||||
}
|
||||
};
|
||||
|
||||
getStatusAlias = () => {
|
||||
if (Object.keys(store.outputs).length !== 0) {
|
||||
const {targets, host_actions} = store.request;
|
||||
for (let item of [{id: 'local'}, ...(host_actions.length > 0 ? targets : [])]) {
|
||||
if (lds.get(store.outputs, `${item.id}.status`) === 'error') {
|
||||
return <Tag color="red">发布异常</Tag>
|
||||
} else if (lds.get(store.outputs, `${item.id}.step`, -1) < 100) {
|
||||
return <Tag color="orange">发布中</Tag>
|
||||
}
|
||||
}
|
||||
return <Tag color="green">发布成功</Tag>
|
||||
} else {
|
||||
return <Tag color="blue">{store.request['status_alias'] || '...'}</Tag>
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {app_name, env_name, status, server_actions, host_actions} = store.request;
|
||||
return (
|
||||
<AuthDiv auth="deploy.request.do">
|
||||
<Spin spinning={this.state.fetching}>
|
||||
<PageHeader
|
||||
title="应用发布"
|
||||
subTitle={`${app_name} - ${env_name}`}
|
||||
style={{padding: 0}}
|
||||
tags={this.getStatusAlias()}
|
||||
extra={this.log ? (
|
||||
<Button icon={<SyncOutlined />} type="primary" onClick={this.fetch}>刷新</Button>
|
||||
) : (
|
||||
<Button icon={<PlayCircleOutlined />} loading={this.state.loading} type="primary"
|
||||
disabled={!['1', '-3'].includes(status)}
|
||||
onClick={this.handleDeploy}>发布</Button>
|
||||
)}
|
||||
onBack={() => history.goBack()}/>
|
||||
<Collapse defaultActiveKey={1} className={styles.collapse}>
|
||||
<Collapse.Panel showArrow={false} key={1} header={
|
||||
<Steps style={{maxWidth: 400 + server_actions.length * 200}}>
|
||||
<Steps.Step {...this.getStatus('local', 0)} title="建立连接"/>
|
||||
<Steps.Step {...this.getStatus('local', 1)} title="发布准备"/>
|
||||
{server_actions.map((item, index) => (
|
||||
<Steps.Step {...this.getStatus('local', 2 + index)} key={index} title={item.title}/>
|
||||
))}
|
||||
</Steps>}>
|
||||
<OutView id="local"/>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
||||
{host_actions.length > 0 && (
|
||||
<Collapse
|
||||
defaultActiveKey={'0'}
|
||||
className={styles.collapse}
|
||||
expandIcon={({isActive}) => <CaretRightOutlined style={{fontSize: 16}} rotate={isActive ? 90 : 0} />}>
|
||||
{store.request.targets.map((item, index) => (
|
||||
<Collapse.Panel key={index} header={
|
||||
<div style={{display: 'flex', justifyContent: 'space-between'}}>
|
||||
<b>{item.title}</b>
|
||||
<Steps size="small" style={{maxWidth: 150 + host_actions.length * 150}}>
|
||||
<Steps.Step {...this.getStatus(item.id, 1)} title="数据准备"/>
|
||||
{host_actions.map((action, index) => (
|
||||
<Steps.Step {...this.getStatus(item.id, 2 + index)} key={index} title={action.title}/>
|
||||
))}
|
||||
</Steps>
|
||||
</div>}>
|
||||
<OutView id={item.id}/>
|
||||
</Collapse.Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
)}
|
||||
{host_actions.length === 0 && this.state.fetching === false && (
|
||||
<div className={styles.ext2Tips}>无目标主机动作</div>
|
||||
)}
|
||||
</Spin>
|
||||
</AuthDiv>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Ext1Index
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* 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 { toJS } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import styles from './index.module.css';
|
||||
import store from './store';
|
||||
import lds from 'lodash';
|
||||
|
||||
@observer
|
||||
class OutView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.el = null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
setTimeout(() => {
|
||||
if (this.el) this.el.scrollTop = this.el.scrollHeight
|
||||
}, 100)
|
||||
}
|
||||
|
||||
render() {
|
||||
const outputs = lds.get(store.outputs, `${this.props.id}.data`, []);
|
||||
return (
|
||||
<pre ref={el => this.el = el} className={styles.ext1Console}>
|
||||
{toJS(outputs)}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default OutView
|
|
@ -1,49 +0,0 @@
|
|||
.header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.collapse :global(.ant-collapse-content-box) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ext1Console {
|
||||
min-height: 40px;
|
||||
max-height: 300px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.ext2Block {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
margin-top: 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.ext2Console {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.ext2Step {
|
||||
padding: 24px;
|
||||
width: 220px;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.ext2Step :global(.ant-steps-item) {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ext2Tips {
|
||||
color: #888;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* Released under the AGPL-3.0 License.
|
||||
*/
|
||||
import { observable } from "mobx";
|
||||
|
||||
class Store {
|
||||
@observable outputs = {};
|
||||
@observable request = {
|
||||
targets: [],
|
||||
host_actions: [],
|
||||
server_actions: []
|
||||
};
|
||||
}
|
||||
|
||||
export default new Store()
|
|
@ -25,8 +25,6 @@ import ExecTemplate from './pages/exec/template';
|
|||
import DeployApp from './pages/deploy/app';
|
||||
import DeployRepository from './pages/deploy/repository';
|
||||
import DeployRequest from './pages/deploy/request';
|
||||
import DoExt1Index from './pages/deploy/do/Ext1Index';
|
||||
import DoExt2Index from './pages/deploy/do/Ext2Index';
|
||||
import ScheduleIndex from './pages/schedule';
|
||||
import ConfigEnvironment from './pages/config/environment';
|
||||
import ConfigService from './pages/config/service';
|
||||
|
@ -63,10 +61,6 @@ export default [
|
|||
{title: '应用管理', auth: 'deploy.app.view', path: '/deploy/app', component: DeployApp},
|
||||
{title: '构建仓库', auth: 'deploy.repository.view', path: '/deploy/repository', component: DeployRepository},
|
||||
{title: '发布申请', auth: 'deploy.request.view', path: '/deploy/request', component: DeployRequest},
|
||||
{path: '/deploy/do/ext1/:id', component: DoExt1Index},
|
||||
{path: '/deploy/do/ext2/:id', component: DoExt2Index},
|
||||
{path: '/deploy/do/ext1/:id/:log', component: DoExt1Index},
|
||||
{path: '/deploy/do/ext2/:id/:log', component: DoExt2Index},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue