style migrate v3

pull/289/head
vapao 2020-11-26 17:41:54 +08:00
parent 97bbe7ddf0
commit b875d9d65a
15 changed files with 273 additions and 309 deletions

View File

@ -18,10 +18,10 @@ export default observer(function () {
return ( return (
<AuthDiv auth="deploy.app.view"> <AuthDiv auth="deploy.app.view">
<Breadcrumb> <Breadcrumb>
<Breadcrumb.Item>首页</Breadcrumb.Item> <Breadcrumb.Item>首页</Breadcrumb.Item>
<Breadcrumb.Item>应用发布</Breadcrumb.Item> <Breadcrumb.Item>应用发布</Breadcrumb.Item>
<Breadcrumb.Item>应用管理</Breadcrumb.Item> <Breadcrumb.Item>应用管理</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
<SearchForm> <SearchForm>
<SearchForm.Item span={7} title="应用名称"> <SearchForm.Item span={7} title="应用名称">
<Input allowClear value={store.f_name} onChange={e => store.f_name = e.target.value} placeholder="请输入"/> <Input allowClear value={store.f_name} onChange={e => store.f_name = e.target.value} placeholder="请输入"/>
@ -31,10 +31,10 @@ export default observer(function () {
</SearchForm.Item> </SearchForm.Item>
</SearchForm> </SearchForm>
<ComTable/> <ComTable/>
{store.formVisible && <ComForm />} {store.formVisible && <ComForm/>}
{store.addVisible && <AddSelect />} {store.addVisible && <AddSelect/>}
{store.ext1Visible && <Ext1Form />} {store.ext1Visible && <Ext1Form/>}
{store.ext2Visible && <Ext2Form />} {store.ext2Visible && <Ext2Form/>}
</AuthDiv> </AuthDiv>
); );
}) })

View File

@ -5,7 +5,8 @@
*/ */
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Steps, Collapse, PageHeader, Spin, Tag, Button, Icon } from 'antd'; 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 { http, history, X_TOKEN } from 'libs';
import { AuthDiv } from 'components'; import { AuthDiv } from 'components';
import OutView from './OutView'; import OutView from './OutView';
@ -96,7 +97,7 @@ class Ext1Index extends React.Component {
getStatus = (key, n) => { getStatus = (key, n) => {
const step = lds.get(store.outputs, `${key}.step`, -1); const step = lds.get(store.outputs, `${key}.step`, -1);
const isError = lds.get(store.outputs, `${key}.status`) === 'error'; const isError = lds.get(store.outputs, `${key}.status`) === 'error';
const icon = <Icon type="loading"/>; const icon = <LoadingOutlined />;
if (n > step) { if (n > step) {
return {key: n, status: 'wait'} return {key: n, status: 'wait'}
} else if (n === step) { } else if (n === step) {
@ -112,12 +113,12 @@ class Ext1Index extends React.Component {
if (lds.get(store.outputs, `${item.id}.status`) === 'error') { if (lds.get(store.outputs, `${item.id}.status`) === 'error') {
return <Tag color="red">发布异常</Tag> return <Tag color="red">发布异常</Tag>
} else if (lds.get(store.outputs, `${item.id}.step`, -1) < 5) { } else if (lds.get(store.outputs, `${item.id}.step`, -1) < 5) {
return <Tag color="blue">发布中</Tag> return <Tag color="orange">发布中</Tag>
} }
} }
return <Tag color="green">发布成功</Tag> return <Tag color="green">发布成功</Tag>
} else { } else {
return <Tag>{store.request['status_alias'] || '...'}</Tag> return <Tag color="blue">{store.request['status_alias'] || '...'}</Tag>
} }
}; };
@ -132,9 +133,9 @@ class Ext1Index extends React.Component {
style={{padding: 0}} style={{padding: 0}}
tags={this.getStatusAlias()} tags={this.getStatusAlias()}
extra={this.log ? ( extra={this.log ? (
<Button icon="sync" type="primary" onClick={this.fetch}>刷新</Button> <Button icon={<SyncOutlined />} type="primary" onClick={this.fetch}>刷新</Button>
) : ( ) : (
<Button icon="play-circle" loading={this.state.loading} type="primary" <Button icon={<PlayCircleOutlined />} loading={this.state.loading} type="primary"
disabled={!['1', '-3'].includes(status)} disabled={!['1', '-3'].includes(status)}
onClick={this.handleDeploy}>发布</Button> onClick={this.handleDeploy}>发布</Button>
)} )}
@ -156,7 +157,7 @@ class Ext1Index extends React.Component {
<Collapse <Collapse
defaultActiveKey={'0'} defaultActiveKey={'0'}
className={styles.collapse} className={styles.collapse}
expandIcon={({isActive}) => <Icon type="caret-right" style={{fontSize: 16}} rotate={isActive ? 90 : 0}/>}> expandIcon={({isActive}) => <CaretRightOutlined style={{fontSize: 16}} rotate={isActive ? 90 : 0} />}>
{store.request.targets.map((item, index) => ( {store.request.targets.map((item, index) => (
<Collapse.Panel key={index} header={ <Collapse.Panel key={index} header={
<div style={{display: 'flex', justifyContent: 'space-between'}}> <div style={{display: 'flex', justifyContent: 'space-between'}}>

View File

@ -5,7 +5,8 @@
*/ */
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Steps, Collapse, PageHeader, Spin, Tag, Button, Icon } from 'antd'; 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 { http, history, X_TOKEN } from 'libs';
import { AuthDiv } from 'components'; import { AuthDiv } from 'components';
import OutView from './OutView'; import OutView from './OutView';
@ -97,7 +98,7 @@ class Ext1Index extends React.Component {
getStatus = (key, n) => { getStatus = (key, n) => {
const step = lds.get(store.outputs, `${key}.step`, -1); const step = lds.get(store.outputs, `${key}.step`, -1);
const isError = lds.get(store.outputs, `${key}.status`) === 'error'; const isError = lds.get(store.outputs, `${key}.status`) === 'error';
const icon = <Icon type="loading"/>; const icon = <LoadingOutlined />;
if (n > step) { if (n > step) {
return {key: n, status: 'wait'} return {key: n, status: 'wait'}
} else if (n === step) { } else if (n === step) {
@ -114,12 +115,12 @@ class Ext1Index extends React.Component {
if (lds.get(store.outputs, `${item.id}.status`) === 'error') { if (lds.get(store.outputs, `${item.id}.status`) === 'error') {
return <Tag color="red">发布异常</Tag> return <Tag color="red">发布异常</Tag>
} else if (lds.get(store.outputs, `${item.id}.step`, -1) < 100) { } else if (lds.get(store.outputs, `${item.id}.step`, -1) < 100) {
return <Tag color="blue">发布中</Tag> return <Tag color="orange">发布中</Tag>
} }
} }
return <Tag color="green">发布成功</Tag> return <Tag color="green">发布成功</Tag>
} else { } else {
return <Tag>{store.request['status_alias'] || '...'}</Tag> return <Tag color="blue">{store.request['status_alias'] || '...'}</Tag>
} }
}; };
@ -134,9 +135,9 @@ class Ext1Index extends React.Component {
style={{padding: 0}} style={{padding: 0}}
tags={this.getStatusAlias()} tags={this.getStatusAlias()}
extra={this.log ? ( extra={this.log ? (
<Button icon="sync" type="primary" onClick={this.fetch}>刷新</Button> <Button icon={<SyncOutlined />} type="primary" onClick={this.fetch}>刷新</Button>
) : ( ) : (
<Button icon="play-circle" loading={this.state.loading} type="primary" <Button icon={<PlayCircleOutlined />} loading={this.state.loading} type="primary"
disabled={!['1', '-3'].includes(status)} disabled={!['1', '-3'].includes(status)}
onClick={this.handleDeploy}>发布</Button> onClick={this.handleDeploy}>发布</Button>
)} )}
@ -158,7 +159,7 @@ class Ext1Index extends React.Component {
<Collapse <Collapse
defaultActiveKey={'0'} defaultActiveKey={'0'}
className={styles.collapse} className={styles.collapse}
expandIcon={({isActive}) => <Icon type="caret-right" style={{fontSize: 16}} rotate={isActive ? 90 : 0}/>}> expandIcon={({isActive}) => <CaretRightOutlined style={{fontSize: 16}} rotate={isActive ? 90 : 0} />}>
{store.request.targets.map((item, index) => ( {store.request.targets.map((item, index) => (
<Collapse.Panel key={index} header={ <Collapse.Panel key={index} header={
<div style={{display: 'flex', justifyContent: 'space-between'}}> <div style={{display: 'flex', justifyContent: 'space-between'}}>

View File

@ -3,58 +3,50 @@
* 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 { Modal, Form, Input, Switch, message } from 'antd'; import { Modal, Form, Input, Switch, message } from 'antd';
import http from 'libs/http'; import http from 'libs/http';
import store from './store'; import store from './store';
@observer export default observer(function () {
class Approve extends React.Component { const [form] = Form.useForm();
constructor(props) { const [isPass, setIsPass] = useState(true);
super(props); const [loading, setLoading] = useState(false);
this.state = {
loading: false,
}
}
handleSubmit = () => { function handleSubmit() {
this.setState({loading: true}); setLoading(true);
const formData = this.props.form.getFieldsValue(); const formData = form.getFieldsValue();
http.patch(`/api/deploy/request/${store.record.id}/`, formData) http.patch(`/api/deploy/request/${store.record.id}/`, formData)
.then(res => { .then(res => {
message.success('操作成功'); message.success('操作成功');
store.approveVisible = false; store.approveVisible = false;
store.fetchRecords() store.fetchRecords()
}, () => this.setState({loading: false})) }, () => setLoading(false))
};
render() {
const {getFieldDecorator, getFieldValue} = this.props.form;
return (
<Modal
visible
width={600}
maskClosable={false}
title="审核发布申请"
onCancel={() => store.approveVisible = false}
confirmLoading={this.state.loading}
onOk={this.handleSubmit}>
<Form labelCol={{span: 6}} wrapperCol={{span: 14}}>
<Form.Item required label="审批结果">
{getFieldDecorator('is_pass', {initialValue: true, valuePropName: "checked"})(
<Switch checkedChildren="通过" unCheckedChildren="驳回"/>
)}
</Form.Item>
<Form.Item required={getFieldValue('is_pass') === false} label={getFieldValue('is_pass') ? '审批意见' : '驳回原因'}>
{getFieldDecorator('reason')(
<Input.TextArea placeholder={getFieldValue('is_pass') ? '请输入审批意见' : '请输入驳回原因'}/>
)}
</Form.Item>
</Form>
</Modal>
)
} }
}
export default Form.create()(Approve) function handleChange(val) {
if (val.is_pass !== undefined) {
setIsPass(val.is_pass)
}
}
return (
<Modal
visible
width={600}
maskClosable={false}
title="审核发布申请"
onCancel={() => store.approveVisible = false}
confirmLoading={loading}
onOk={handleSubmit}>
<Form form={form} labelCol={{span: 6}} wrapperCol={{span: 14}} onValuesChange={handleChange}>
<Form.Item required name="is_pass" initialValue={true} valuePropName="checked" label="审批结果">
<Switch checkedChildren="通过" unCheckedChildren="驳回"/>
</Form.Item>
<Form.Item name="reason" required={isPass === false} label={isPass ? '审批意见' : '驳回原因'}>
<Input.TextArea placeholder={isPass ? '请输入审批意见' : '请输入驳回原因'}/>
</Form.Item>
</Form>
</Modal>
)
})

View File

@ -3,47 +3,59 @@
* 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, useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Modal, Form, Input, Select, Col, Button, Tag, Icon, message } from 'antd'; import { LoadingOutlined, SyncOutlined } from '@ant-design/icons';
import { Modal, Form, Input, Select, Button, Tag, message } from 'antd';
import hostStore from 'pages/host/store'; import hostStore from 'pages/host/store';
import http from 'libs/http'; import http from 'libs/http';
import store from './store'; import store from './store';
import lds from 'lodash'; import lds from 'lodash';
@observer export default observer(function () {
class Ext1Form extends React.Component { const [form] = Form.useForm();
constructor(props) { const [loading, setLoading] = useState(false);
super(props); const [fetching, setFetching] = useState(true);
this.isReady = false; const [git_type, setGitType] = useState(lds.get(store.record, 'extra.0', 'branch'));
this.state = { const [extra1, setExtra1] = useState(lds.get(store.record, 'extra.1'));
loading: false, const [extra2, setExtra2] = useState(lds.get(store.record, 'extra.2'));
fetching: true, const [versions, setVersions] = useState({});
git_type: lds.get(store.record, 'extra.0', 'branch'), const [host_ids, setHostIds] = useState(lds.clone(store.record.app_host_ids));
extra1: lds.get(store.record, 'extra.1'),
extra2: lds.get(store.record, 'extra.2'),
versions: {},
host_ids: store.record['app_host_ids'].concat()
}
}
componentDidMount() { useEffect(() => {
this.fetchVersions(); fetchVersions();
if (hostStore.records.length === 0) { if (hostStore.records.length === 0) {
hostStore.fetchRecords() hostStore.fetchRecords()
} }
}, [])
useEffect(() => {
if (extra1 === undefined) {
const {branches, tags} = versions;
let [extra1, extra2] = [undefined, undefined];
if (git_type === 'branch') {
if (branches) {
extra1 = _getDefaultBranch(branches);
extra2 = lds.get(branches[extra1], '0.id')
}
} else {
if (tags) {
extra1 = lds.get(Object.keys(tags), 0)
}
}
setExtra1(extra1)
setExtra2(extra2)
}
}, [versions, git_type, extra1])
function fetchVersions() {
setFetching(true);
http.get(`/api/app/deploy/${store.record.deploy_id}/versions/`, {timeout: 120000})
.then(res => setVersions(res))
.finally(() => setFetching(false))
} }
fetchVersions = () => { function _getDefaultBranch(branches) {
this.setState({fetching: true});
http.get(`/api/app/deploy/${store.record.deploy_id}/versions/`, {timeout: 120000})
.then(res => {
this.setState({versions: res}, this._initExtra1);
})
.finally(() => this.setState({fetching: false}))
};
_getDefaultBranch = (branches) => {
branches = Object.keys(branches); branches = Object.keys(branches);
let branch = branches[0]; let branch = branches[0];
for (let item of store.records) { for (let item of store.records) {
@ -56,149 +68,117 @@ class Ext1Form extends React.Component {
} }
} }
return branch return branch
}; }
_initExtra1 = () => { function switchType(v) {
if (this.isReady === true || this.state.extra1 === undefined) { setExtra1(undefined);
const {git_type, versions: {branches, tags}} = this.state; setGitType(v)
let [extra1, extra2] = [undefined, undefined]; }
if (git_type === 'branch') {
if (branches) {
extra1 = this._getDefaultBranch(branches);
extra2 = lds.get(branches[extra1], '0.id')
}
} else {
if (tags) {
extra1 = lds.get(Object.keys(tags), 0)
}
}
this.setState({extra1, extra2})
} else {
this.isReady = true
}
};
switchType = (v) => { function switchExtra1(v) {
this.setState({git_type: v, extra1: undefined}, this._initExtra1) setExtra1(v)
};
switchExtra1 = (v) => {
let {git_type, extra2, versions: {branches}} = this.state;
if (git_type === 'branch') { if (git_type === 'branch') {
extra2 = lds.get(branches[v], '0.id') setExtra2(lds.get(versions.branches[v], '0.id'))
} }
this.setState({extra1: v, extra2}) }
};
handleSubmit = () => { function handleSubmit() {
if (this.state.host_ids.length === 0) { if (host_ids.length === 0) {
return message.error('请至少选择一个要发布的目标主机') return message.error('请至少选择一个要发布的目标主机')
} }
this.setState({loading: true}); setLoading(true);
const {git_type, extra1, extra2} = this.state; const formData = form.getFieldsValue();
const formData = this.props.form.getFieldsValue();
formData['id'] = store.record.id; formData['id'] = store.record.id;
formData['deploy_id'] = store.record.deploy_id; formData['deploy_id'] = store.record.deploy_id;
formData['host_ids'] = this.state.host_ids; formData['host_ids'] = host_ids;
formData['extra'] = [git_type, extra1, extra2]; formData['extra'] = [git_type, extra1, extra2];
http.post('/api/deploy/request/', formData) http.post('/api/deploy/request/', formData)
.then(res => { .then(res => {
message.success('操作成功'); message.success('操作成功');
store.ext1Visible = false; store.ext1Visible = false;
store.fetchRecords() store.fetchRecords()
}, () => this.setState({loading: false})) }, () => setLoading(false))
}; }
handleChange = (id) => { function handleChange(id) {
const host_ids = this.state.host_ids;
const index = host_ids.indexOf(id); const index = host_ids.indexOf(id);
if (index === -1) { if (index === -1) {
this.setState({host_ids: [id, ...host_ids]}) setHostIds([id, ...host_ids])
} else { } else {
host_ids.splice(index, 1); host_ids.splice(index, 1);
this.setState({host_ids}) setHostIds(host_ids)
} }
}; }
render() { const {branches, tags} = versions;
const info = store.record; return (
const {host_ids, git_type, extra1, extra2, fetching, versions: {branches, tags}} = this.state; <Modal
const {getFieldDecorator} = this.props.form; visible
return ( width={800}
<Modal maskClosable={false}
visible title="新建发布申请"
width={800} onCancel={() => store.ext1Visible = false}
maskClosable={false} confirmLoading={loading}
title="新建发布申请" onOk={handleSubmit}>
onCancel={() => store.ext1Visible = false} <Form form={form} initialValues={store.record} labelCol={{span: 5}} wrapperCol={{span: 17}}>
confirmLoading={this.state.loading} <Form.Item required name="name" label="申请标题">
onOk={this.handleSubmit}> <Input placeholder="请输入申请标题"/>
<Form labelCol={{span: 5}} wrapperCol={{span: 17}}> </Form.Item>
<Form.Item required label="申请标题"> <Form.Item required label="选择分支/标签/版本" style={{marginBottom: 12}} extra={<span>
{getFieldDecorator('name', {initialValue: info['name']})(
<Input placeholder="请输入申请标题"/>
)}
</Form.Item>
<Form.Item required label="选择分支/标签/版本" extra={<span>
根据网络情况首次刷新可能会很慢请耐心等待 根据网络情况首次刷新可能会很慢请耐心等待
<a target="_blank" rel="noopener noreferrer" <a target="_blank" rel="noopener noreferrer"
href="https://spug.dev/docs/install-error/#%E6%96%B0%E5%BB%BA%E5%B8%B8%E8%A7%84%E5%8F%91%E5%B8%83%E7%94%B3%E8%AF%B7-git-clone-%E9%94%99%E8%AF%AF">clone 失败</a> href="https://spug.dev/docs/install-error/#%E6%96%B0%E5%BB%BA%E5%B8%B8%E8%A7%84%E5%8F%91%E5%B8%83%E7%94%B3%E8%AF%B7-git-clone-%E9%94%99%E8%AF%AF">clone 失败</a>
</span>}> </span>}>
<Col span={19}> <Form.Item style={{display: 'inline-block', marginBottom: 0, width: '450px'}}>
<Input.Group compact> <Input.Group compact>
<Select value={git_type} onChange={this.switchType} style={{width: 100}}> <Select value={git_type} onChange={switchType} style={{width: 100}}>
<Select.Option value="branch">Branch</Select.Option> <Select.Option value="branch">Branch</Select.Option>
<Select.Option value="tag">Tag</Select.Option> <Select.Option value="tag">Tag</Select.Option>
</Select>
<Select
showSearch
style={{width: 320}}
value={extra1}
placeholder="请稍等"
onChange={this.switchExtra1}
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}>
{git_type === 'branch' ? (
Object.keys(branches || {}).map(b => <Select.Option key={b} value={b}>{b}</Select.Option>)
) : (
Object.entries(tags || {}).map(([tag, info]) => (
<Select.Option key={tag} value={tag}>{`${tag} ${info.author} ${info.message}`}</Select.Option>
))
)}
</Select>
</Input.Group>
</Col>
<Col span={4} offset={1} style={{textAlign: 'center'}}>
{fetching ? <Icon type="loading" style={{fontSize: 18, color: '#1890ff'}}/> :
<Button type="link" icon="sync" disabled={fetching} onClick={this.fetchVersions}>刷新</Button>
}
</Col>
</Form.Item>
{git_type === 'branch' && (
<Form.Item required label="选择Commit ID">
<Select value={extra2} placeholder="请选择" onChange={v => this.setState({extra2: v})}>
{extra1 && branches ? branches[extra1].map(item => (
<Select.Option
key={item.id}>{item.id.substr(0, 6)} {item['date']} {item['author']} {item['message']}</Select.Option>
)) : null}
</Select> </Select>
</Form.Item> <Select
)} showSearch
<Form.Item label="备注信息"> style={{width: 350}}
{getFieldDecorator('desc', {initialValue: info['desc']})( value={extra1}
<Input placeholder="请输入备注信息"/> placeholder="请稍等"
)} onChange={switchExtra1}
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}>
{git_type === 'branch' ? (
Object.keys(branches || {}).map(b => <Select.Option key={b} value={b}>{b}</Select.Option>)
) : (
Object.entries(tags || {}).map(([tag, info]) => (
<Select.Option key={tag} value={tag}>{`${tag} ${info.author} ${info.message}`}</Select.Option>
))
)}
</Select>
</Input.Group>
</Form.Item> </Form.Item>
<Form.Item required label="发布目标主机" help="通过点击主机名称自由选择本次发布的主机。"> <Form.Item style={{display: 'inline-block', width: 82, textAlign: 'center', marginBottom: 0}}>
{info['app_host_ids'].map(id => ( {fetching ? <LoadingOutlined style={{fontSize: 18, color: '#1890ff'}}/> :
<Tag.CheckableTag key={id} checked={host_ids.includes(id)} onChange={() => this.handleChange(id)}> <Button type="link" icon={<SyncOutlined/>} disabled={fetching} onClick={fetchVersions}>刷新</Button>
{lds.get(hostStore.idMap, `${id}.name`)}({lds.get(hostStore.idMap, `${id}.hostname`)}:{lds.get(hostStore.idMap, `${id}.port`)}) }
</Tag.CheckableTag>
))}
</Form.Item> </Form.Item>
</Form> </Form.Item>
</Modal> {git_type === 'branch' && (
) <Form.Item required label="选择Commit ID">
} <Select value={extra2} placeholder="请选择" onChange={v => setExtra2(v)}>
} {extra1 && branches ? branches[extra1].map(item => (
<Select.Option
export default Form.create()(Ext1Form) key={item.id}>{item.id.substr(0, 6)} {item['date']} {item['author']} {item['message']}</Select.Option>
)) : null}
</Select>
</Form.Item>
)}
<Form.Item name="desc" label="备注信息">
<Input placeholder="请输入备注信息"/>
</Form.Item>
<Form.Item required label="发布目标主机" help="通过点击主机名称自由选择本次发布的主机。">
{store.record['app_host_ids'].map(id => (
<Tag.CheckableTag key={id} checked={host_ids.includes(id)} onChange={() => handleChange(id)}>
{lds.get(hostStore.idMap, `${id}.name`)}({lds.get(hostStore.idMap, `${id}.hostname`)}:{lds.get(hostStore.idMap, `${id}.port`)})
</Tag.CheckableTag>
))}
</Form.Item>
</Form>
</Modal>
)
})

View File

@ -6,7 +6,8 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Modal, Button, Menu, Spin, Icon, Input, Tooltip } from 'antd'; import { Modal, Button, Menu, Spin, Input, Tooltip } from 'antd';
import { OrderedListOutlined, BuildOutlined } from '@ant-design/icons';
import store from './store'; import store from './store';
import styles from './index.module.css'; import styles from './index.module.css';
import envStore from 'pages/config/environment/store'; import envStore from 'pages/config/environment/store';
@ -100,8 +101,8 @@ class SelectApp extends React.Component {
<Button type="primary" className={styles.appBlock} onClick={() => this.handleClick(item)}> <Button type="primary" className={styles.appBlock} onClick={() => this.handleClick(item)}>
<div ref={el => this.handleRef(el, item.id)} <div ref={el => this.handleRef(el, item.id)}
style={{width: 135, overflow: 'hidden', textOverflow: 'ellipsis'}}> style={{width: 135, overflow: 'hidden', textOverflow: 'ellipsis'}}>
<Icon type={item.extend === '1' ? 'ordered-list' : 'build'} {item.extend === '1' ? <OrderedListOutlined/> : <BuildOutlined/>}
style={{marginRight: 10}}/>{item['app_name']} <span style={{marginLeft: 8}}>{item.app_name}</span>
</div> </div>
</Button> </Button>
</Tooltip> </Tooltip>

View File

@ -5,9 +5,10 @@
*/ */
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Table, Modal, Icon, Popover, Tag, message } from 'antd'; import { BranchesOutlined, BuildOutlined, TagOutlined, PlusOutlined } from '@ant-design/icons';
import { Radio, Modal, Popover, Tag, message } from 'antd';
import { http, hasPermission } from 'libs'; import { http, hasPermission } from 'libs';
import { Action } from "components"; import { Action, AuthButton, TableCard } from 'components';
import store from './store'; import store from './store';
@observer @observer
@ -25,7 +26,12 @@ class ComTable extends React.Component {
columns = [{ columns = [{
title: '申请标题', title: '申请标题',
dataIndex: 'name', render: info => (
<div>
{info.type === '2' && <Tag color="#f50">R</Tag>}
{info.name}
</div>
)
}, { }, {
title: '应用', title: '应用',
dataIndex: 'app_name', dataIndex: 'app_name',
@ -38,18 +44,24 @@ class ComTable extends React.Component {
if (info['app_extend'] === '1') { if (info['app_extend'] === '1') {
const [type, ext1, ext2] = info.extra; const [type, ext1, ext2] = info.extra;
if (type === 'branch') { if (type === 'branch') {
return <React.Fragment> return (
<Icon type="branches"/> {ext1}#{ext2.substr(0, 6)} <React.Fragment>
</React.Fragment> <BranchesOutlined/> {ext1}#{ext2.substr(0, 6)}
</React.Fragment>
)
} else { } else {
return <React.Fragment> return (
<Icon type="tag"/> {ext1} <React.Fragment>
</React.Fragment> <TagOutlined/> {ext1}
</React.Fragment>
)
} }
} else { } else {
return <React.Fragment> return (
<Icon type="build"/> {info.extra[0]} <React.Fragment>
</React.Fragment> <BuildOutlined/> {info.extra[0]}
</React.Fragment>
)
} }
} }
}, { }, {
@ -64,13 +76,13 @@ class ComTable extends React.Component {
<span style={{color: '#1890ff'}}>{info['status_alias']}</span> <span style={{color: '#1890ff'}}>{info['status_alias']}</span>
</Popover> </Popover>
} else if (info.status === '2') { } else if (info.status === '2') {
return <Tag color="blue">{info['status_alias']}</Tag> return <Tag color="orange">{info['status_alias']}</Tag>
} else if (info.status === '3') { } else if (info.status === '3') {
return <Tag color="green">{info['status_alias']}</Tag> return <Tag color="green">{info['status_alias']}</Tag>
} else if (info.status === '-3') { } else if (info.status === '-3') {
return <Tag color="red">{info['status_alias']}</Tag> return <Tag color="red">{info['status_alias']}</Tag>
} else { } else {
return <Tag>{info['status_alias']}</Tag> return <Tag color="blue">{info['status_alias']}</Tag>
} }
} }
}, { }, {
@ -80,6 +92,11 @@ class ComTable extends React.Component {
title: '申请时间', title: '申请时间',
dataIndex: 'created_at', dataIndex: 'created_at',
sorter: (a, b) => a['created_at'].localeCompare(b['created_at']) sorter: (a, b) => a['created_at'].localeCompare(b['created_at'])
}, {
title: '备注',
dataIndex: 'desc',
ellipsis: true,
hide: true
}, { }, {
title: '操作', title: '操作',
className: hasPermission('deploy.request.do|deploy.request.edit|deploy.request.approve|deploy.request.del') ? null : 'none', className: hasPermission('deploy.request.do|deploy.request.edit|deploy.request.approve|deploy.request.del') ? null : 'none',
@ -191,10 +208,27 @@ class ComTable extends React.Component {
} }
} }
return ( return (
<Table <TableCard
rowKey="id" rowKey="id"
title="申请列表"
loading={store.isFetching} loading={store.isFetching}
dataSource={data} dataSource={data}
onReload={store.fetchRecords}
actions={[
<AuthButton
auth="deploy.request.add"
type="primary"
icon={<PlusOutlined/>}
onClick={() => store.addVisible = true}>新建申请</AuthButton>,
<Radio.Group value={store.f_status} onChange={e => store.f_status = e.target.value}>
<Radio.Button value="all">全部({store.counter['all'] || 0})</Radio.Button>
<Radio.Button value="0">待审核({store.counter['0'] || 0})</Radio.Button>
<Radio.Button value="1">待发布({store.counter['1'] || 0})</Radio.Button>
<Radio.Button value="3">发布成功({store.counter['3'] || 0})</Radio.Button>
<Radio.Button value="-3">发布异常({store.counter['-3'] || 0})</Radio.Button>
<Radio.Button value="99">其他({store.counter['99'] || 0})</Radio.Button>
</Radio.Group>
]}
pagination={{ pagination={{
showSizeChanger: true, showSizeChanger: true,
showLessItems: true, showLessItems: true,

View File

@ -5,8 +5,9 @@
*/ */
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Button, Select, DatePicker, Radio, Row, Col, Modal, Form, Input, message } from 'antd'; import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons';
import { SearchForm, AuthFragment, AuthCard } from 'components'; import { Form, Select, DatePicker, Modal, Input, message } from 'antd';
import { SearchForm, AuthDiv, AuthButton, Breadcrumb } from 'components';
import SelectApp from './SelectApp'; import SelectApp from './SelectApp';
import Ext1Form from './Ext1Form'; import Ext1Form from './Ext1Form';
import Ext2Form from './Ext2Form'; import Ext2Form from './Ext2Form';
@ -39,15 +40,15 @@ class Index extends React.Component {
handleBatchDel = () => { handleBatchDel = () => {
Modal.confirm({ Modal.confirm({
icon: 'exclamation-circle', icon: <ExclamationCircleOutlined/>,
title: '批量删除发布申请', title: '批量删除发布申请',
content: ( content: (
<Form> <Form layout="vertical" style={{marginTop: 24}}>
<Form.Item label="截止日期" help={<div>将删除截止日期<span style={{color: 'red'}}>之前</span></div>}> <Form.Item label="截止日期 :" help={<div>将删除截止日期<span style={{color: 'red'}}>之前</span></div>}>
<DatePicker style={{width: 200}} placeholder="请输入" <DatePicker style={{width: 200}} placeholder="请输入"
onChange={val => this.setState({expire: val.format('YYYY-MM-DD')})}/> onChange={val => this.setState({expire: val.format('YYYY-MM-DD')})}/>
</Form.Item> </Form.Item>
<Form.Item label="保留记录" help="每个应用每个环境仅保留最新的N条发布申请优先级高于截止日期"> <Form.Item label="保留记录 :" help="每个应用每个环境仅保留最新的N条发布申请优先级高于截止日期">
<Input allowClear style={{width: 200}} placeholder="请输入保留个数" <Input allowClear style={{width: 200}} placeholder="请输入保留个数"
onChange={e => this.setState({count: e.target.value})}/> onChange={e => this.setState({count: e.target.value})}/>
</Form.Item> </Form.Item>
@ -66,7 +67,12 @@ class Index extends React.Component {
render() { render() {
return ( return (
<AuthCard auth="deploy.request.view"> <AuthDiv auth="deploy.request.view">
<Breadcrumb>
<Breadcrumb.Item>首页</Breadcrumb.Item>
<Breadcrumb.Item>应用发布</Breadcrumb.Item>
<Breadcrumb.Item>发布申请</Breadcrumb.Item>
</Breadcrumb>
<SearchForm> <SearchForm>
<SearchForm.Item span={6} title="发布环境"> <SearchForm.Item span={6} title="发布环境">
<Select allowClear value={store.f_env_id} onChange={v => store.f_env_id = v} placeholder="请选择"> <Select allowClear value={store.f_env_id} onChange={v => store.f_env_id = v} placeholder="请选择">
@ -88,39 +94,19 @@ class Index extends React.Component {
onChange={store.updateDate}/> onChange={store.updateDate}/>
</SearchForm.Item> </SearchForm.Item>
<SearchForm.Item span={4} style={{textAlign: 'right'}}> <SearchForm.Item span={4} style={{textAlign: 'right'}}>
<Button type="primary" icon="sync" onClick={store.fetchRecords}>刷新</Button> <AuthButton
auth="deploy.request.del"
type="danger"
icon={<DeleteOutlined/>}
onClick={this.handleBatchDel}>批量删除</AuthButton>
</SearchForm.Item> </SearchForm.Item>
</SearchForm> </SearchForm>
<Row style={{marginBottom: 16}}>
<Col span={16}>
<Radio.Group value={store.f_status} onChange={e => store.f_status = e.target.value}>
<Radio.Button value="all">全部({store.counter['all'] || 0})</Radio.Button>
<Radio.Button value="0">待审核({store.counter['0'] || 0})</Radio.Button>
<Radio.Button value="1">待发布({store.counter['1'] || 0})</Radio.Button>
<Radio.Button value="3">发布成功({store.counter['3'] || 0})</Radio.Button>
<Radio.Button value="-3">发布异常({store.counter['-3'] || 0})</Radio.Button>
<Radio.Button value="99">其他({store.counter['99'] || 0})</Radio.Button>
</Radio.Group>
</Col>
<Col span={8} style={{textAlign: 'right'}}>
<AuthFragment auth="deploy.request.del">
<Button type="primary" icon="delete" onClick={this.handleBatchDel}>批量删除</Button>
</AuthFragment>
<AuthFragment auth="deploy.request.add">
<Button
type="primary"
icon="plus"
onClick={() => store.addVisible = true}
style={{marginLeft: 20}}>新建发布申请</Button>
</AuthFragment>
</Col>
</Row>
<ComTable/> <ComTable/>
{store.addVisible && <SelectApp/>} {store.addVisible && <SelectApp/>}
{store.ext1Visible && <Ext1Form/>} {store.ext1Visible && <Ext1Form/>}
{store.ext2Visible && <Ext2Form/>} {store.ext2Visible && <Ext2Form/>}
{store.approveVisible && <Approve/>} {store.approveVisible && <Approve/>}
</AuthCard> </AuthDiv>
) )
} }
} }

View File

@ -55,7 +55,7 @@ class Store {
}; };
updateDate = (data) => { updateDate = (data) => {
if (data.length === 2) { if (data && data.length === 2) {
this.f_s_date = data[0].format('YYYY-MM-DD'); this.f_s_date = data[0].format('YYYY-MM-DD');
this.f_e_date = data[1].format('YYYY-MM-DD') this.f_e_date = data[1].format('YYYY-MM-DD')
} else { } else {

View File

@ -1,20 +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 { makeRoute } from "../../libs/router";
import app from './app';
import request from './request';
import doExt1Index from './do/Ext1Index';
import doExt2Index from './do/Ext2Index';
export default [
makeRoute('/app', app),
makeRoute('/request', request),
makeRoute('/do/ext1/:id', doExt1Index),
makeRoute('/do/ext2/:id', doExt2Index),
makeRoute('/do/ext1/:id/:log', doExt1Index),
makeRoute('/do/ext2/:id/:log', doExt2Index),
]

View File

@ -1,14 +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 { makeRoute } from "../../libs/router";
import Template from './template';
import Task from './task';
export default [
makeRoute('/template', Template),
makeRoute('/task', Task),
]

View File

@ -1,11 +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 { lazy } from 'react';
import { makeRoute } from 'libs/router';
export default [
makeRoute('', lazy(() => import('./index'))),
]

View File

@ -64,7 +64,7 @@ export default observer(function () {
} }
const ConfirmForm = (props) => ( const ConfirmForm = (props) => (
<Form> <Form layout="vertical" style={{marginTop: 24}}>
<Form.Item required label="授权密码" help={`用户 ${props.username} 的密码, 该密码仅做首次验证使用,不会存储该密码。`}> <Form.Item required label="授权密码" help={`用户 ${props.username} 的密码, 该密码仅做首次验证使用,不会存储该密码。`}>
<Input.Password onChange={e => setPassword(e.target.value)}/> <Input.Password onChange={e => setPassword(e.target.value)}/>
</Form.Item> </Form.Item>

View File

@ -19,7 +19,7 @@ export default observer(function () {
<Breadcrumb.Item>首页</Breadcrumb.Item> <Breadcrumb.Item>首页</Breadcrumb.Item>
<Breadcrumb.Item>主机管理</Breadcrumb.Item> <Breadcrumb.Item>主机管理</Breadcrumb.Item>
</Breadcrumb> </Breadcrumb>
<SearchForm style={{marginBottom: 16}}> <SearchForm>
<SearchForm.Item span={6} title="主机类别"> <SearchForm.Item span={6} title="主机类别">
<Select allowClear placeholder="请选择" value={store.f_zone} onChange={v => store.f_zone = v}> <Select allowClear placeholder="请选择" value={store.f_zone} onChange={v => store.f_zone = v}>
{store.zones.map(item => ( {store.zones.map(item => (

View File

@ -16,19 +16,29 @@ import {
SettingOutlined SettingOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import HomeIndex from './pages/home'; import HomeIndex from './pages/home';
import HostIndex from './pages/host'; import HostIndex from './pages/host';
import ExecTask from './pages/exec/task'; import ExecTask from './pages/exec/task';
import ExecTemplate from './pages/exec/template'; import ExecTemplate from './pages/exec/template';
import DeployApp from './pages/deploy/app'; import DeployApp from './pages/deploy/app';
import DeployRequest from './pages/deploy/request'; 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 ScheduleIndex from './pages/schedule';
import ConfigEnvironment from './pages/config/environment'; import ConfigEnvironment from './pages/config/environment';
import ConfigService from './pages/config/service'; import ConfigService from './pages/config/service';
import ConfigApp from './pages/config/app'; import ConfigApp from './pages/config/app';
import MonitorIndex from './pages/monitor'; import MonitorIndex from './pages/monitor';
import AlarmIndex from './pages/alarm/alarm'; import AlarmIndex from './pages/alarm/alarm';
import AlarmGroup from './pages/alarm/group'; import AlarmGroup from './pages/alarm/group';
import AlarmContact from './pages/alarm/contact'; import AlarmContact from './pages/alarm/contact';
import SystemAccount from './pages/system/account'; import SystemAccount from './pages/system/account';
import SystemRole from './pages/system/role'; import SystemRole from './pages/system/role';
import SystemSetting from './pages/system/setting'; import SystemSetting from './pages/system/setting';
@ -49,6 +59,10 @@ export default [
icon: <FlagOutlined/>, title: '应用发布', auth: 'deploy.app.view|deploy.request.view', child: [ icon: <FlagOutlined/>, title: '应用发布', auth: 'deploy.app.view|deploy.request.view', child: [
{title: '应用管理', auth: 'deploy.app.view', path: '/deploy/app', component: DeployApp}, {title: '应用管理', auth: 'deploy.app.view', path: '/deploy/app', component: DeployApp},
{title: '发布申请', auth: 'deploy.request.view', path: '/deploy/request', component: DeployRequest}, {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},
] ]
}, },
{ {