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 (
<AuthDiv auth="deploy.app.view">
<Breadcrumb>
<Breadcrumb.Item>首页</Breadcrumb.Item>
<Breadcrumb.Item>应用发布</Breadcrumb.Item>
<Breadcrumb.Item>应用管理</Breadcrumb.Item>
</Breadcrumb>
<Breadcrumb.Item>首页</Breadcrumb.Item>
<Breadcrumb.Item>应用发布</Breadcrumb.Item>
<Breadcrumb.Item>应用管理</Breadcrumb.Item>
</Breadcrumb>
<SearchForm>
<SearchForm.Item span={7} title="应用名称">
<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>
<ComTable/>
{store.formVisible && <ComForm />}
{store.addVisible && <AddSelect />}
{store.ext1Visible && <Ext1Form />}
{store.ext2Visible && <Ext2Form />}
{store.formVisible && <ComForm/>}
{store.addVisible && <AddSelect/>}
{store.ext1Visible && <Ext1Form/>}
{store.ext2Visible && <Ext2Form/>}
</AuthDiv>
);
})

View File

@ -5,7 +5,8 @@
*/
import React from '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 { AuthDiv } from 'components';
import OutView from './OutView';
@ -96,7 +97,7 @@ class Ext1Index extends React.Component {
getStatus = (key, n) => {
const step = lds.get(store.outputs, `${key}.step`, -1);
const isError = lds.get(store.outputs, `${key}.status`) === 'error';
const icon = <Icon type="loading"/>;
const icon = <LoadingOutlined />;
if (n > step) {
return {key: n, status: 'wait'}
} else if (n === step) {
@ -112,12 +113,12 @@ class Ext1Index extends React.Component {
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="blue">发布中</Tag>
return <Tag color="orange">发布中</Tag>
}
}
return <Tag color="green">发布成功</Tag>
} 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}}
tags={this.getStatusAlias()}
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)}
onClick={this.handleDeploy}>发布</Button>
)}
@ -156,7 +157,7 @@ class Ext1Index extends React.Component {
<Collapse
defaultActiveKey={'0'}
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) => (
<Collapse.Panel key={index} header={
<div style={{display: 'flex', justifyContent: 'space-between'}}>

View File

@ -5,7 +5,8 @@
*/
import React from '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 { AuthDiv } from 'components';
import OutView from './OutView';
@ -97,7 +98,7 @@ class Ext1Index extends React.Component {
getStatus = (key, n) => {
const step = lds.get(store.outputs, `${key}.step`, -1);
const isError = lds.get(store.outputs, `${key}.status`) === 'error';
const icon = <Icon type="loading"/>;
const icon = <LoadingOutlined />;
if (n > step) {
return {key: n, status: 'wait'}
} else if (n === step) {
@ -114,12 +115,12 @@ class Ext1Index extends React.Component {
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="blue">发布中</Tag>
return <Tag color="orange">发布中</Tag>
}
}
return <Tag color="green">发布成功</Tag>
} 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}}
tags={this.getStatusAlias()}
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)}
onClick={this.handleDeploy}>发布</Button>
)}
@ -158,7 +159,7 @@ class Ext1Index extends React.Component {
<Collapse
defaultActiveKey={'0'}
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) => (
<Collapse.Panel key={index} header={
<div style={{display: 'flex', justifyContent: 'space-between'}}>

View File

@ -3,58 +3,50 @@
* Copyright (c) <spug.dev@gmail.com>
* Released under the AGPL-3.0 License.
*/
import React from 'react';
import React, { useState } from 'react';
import { observer } from 'mobx-react';
import { Modal, Form, Input, Switch, message } from 'antd';
import http from 'libs/http';
import store from './store';
@observer
class Approve extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
}
}
export default observer(function () {
const [form] = Form.useForm();
const [isPass, setIsPass] = useState(true);
const [loading, setLoading] = useState(false);
handleSubmit = () => {
this.setState({loading: true});
const formData = this.props.form.getFieldsValue();
function handleSubmit() {
setLoading(true);
const formData = form.getFieldsValue();
http.patch(`/api/deploy/request/${store.record.id}/`, formData)
.then(res => {
message.success('操作成功');
store.approveVisible = false;
store.fetchRecords()
}, () => this.setState({loading: 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>
)
}, () => setLoading(false))
}
}
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>
* Released under the AGPL-3.0 License.
*/
import React from 'react';
import React, { useState, useEffect } from '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 http from 'libs/http';
import store from './store';
import lds from 'lodash';
@observer
class Ext1Form extends React.Component {
constructor(props) {
super(props);
this.isReady = false;
this.state = {
loading: false,
fetching: true,
git_type: lds.get(store.record, 'extra.0', 'branch'),
extra1: lds.get(store.record, 'extra.1'),
extra2: lds.get(store.record, 'extra.2'),
versions: {},
host_ids: store.record['app_host_ids'].concat()
}
}
export default observer(function () {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [fetching, setFetching] = useState(true);
const [git_type, setGitType] = useState(lds.get(store.record, 'extra.0', 'branch'));
const [extra1, setExtra1] = useState(lds.get(store.record, 'extra.1'));
const [extra2, setExtra2] = useState(lds.get(store.record, 'extra.2'));
const [versions, setVersions] = useState({});
const [host_ids, setHostIds] = useState(lds.clone(store.record.app_host_ids));
componentDidMount() {
this.fetchVersions();
useEffect(() => {
fetchVersions();
if (hostStore.records.length === 0) {
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 = () => {
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) => {
function _getDefaultBranch(branches) {
branches = Object.keys(branches);
let branch = branches[0];
for (let item of store.records) {
@ -56,149 +68,117 @@ class Ext1Form extends React.Component {
}
}
return branch
};
}
_initExtra1 = () => {
if (this.isReady === true || this.state.extra1 === undefined) {
const {git_type, versions: {branches, tags}} = this.state;
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
}
};
function switchType(v) {
setExtra1(undefined);
setGitType(v)
}
switchType = (v) => {
this.setState({git_type: v, extra1: undefined}, this._initExtra1)
};
switchExtra1 = (v) => {
let {git_type, extra2, versions: {branches}} = this.state;
function switchExtra1(v) {
setExtra1(v)
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 = () => {
if (this.state.host_ids.length === 0) {
function handleSubmit() {
if (host_ids.length === 0) {
return message.error('请至少选择一个要发布的目标主机')
}
this.setState({loading: true});
const {git_type, extra1, extra2} = this.state;
const formData = this.props.form.getFieldsValue();
setLoading(true);
const formData = form.getFieldsValue();
formData['id'] = store.record.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];
http.post('/api/deploy/request/', formData)
.then(res => {
message.success('操作成功');
store.ext1Visible = false;
store.fetchRecords()
}, () => this.setState({loading: false}))
};
}, () => setLoading(false))
}
handleChange = (id) => {
const host_ids = this.state.host_ids;
function handleChange(id) {
const index = host_ids.indexOf(id);
if (index === -1) {
this.setState({host_ids: [id, ...host_ids]})
setHostIds([id, ...host_ids])
} else {
host_ids.splice(index, 1);
this.setState({host_ids})
setHostIds(host_ids)
}
};
}
render() {
const info = store.record;
const {host_ids, git_type, extra1, extra2, fetching, versions: {branches, tags}} = this.state;
const {getFieldDecorator} = this.props.form;
return (
<Modal
visible
width={800}
maskClosable={false}
title="新建发布申请"
onCancel={() => store.ext1Visible = false}
confirmLoading={this.state.loading}
onOk={this.handleSubmit}>
<Form labelCol={{span: 5}} wrapperCol={{span: 17}}>
<Form.Item required label="申请标题">
{getFieldDecorator('name', {initialValue: info['name']})(
<Input placeholder="请输入申请标题"/>
)}
</Form.Item>
<Form.Item required label="选择分支/标签/版本" extra={<span>
const {branches, tags} = versions;
return (
<Modal
visible
width={800}
maskClosable={false}
title="新建发布申请"
onCancel={() => store.ext1Visible = false}
confirmLoading={loading}
onOk={handleSubmit}>
<Form form={form} initialValues={store.record} labelCol={{span: 5}} wrapperCol={{span: 17}}>
<Form.Item required name="name" label="申请标题">
<Input placeholder="请输入申请标题"/>
</Form.Item>
<Form.Item required label="选择分支/标签/版本" style={{marginBottom: 12}} extra={<span>
根据网络情况首次刷新可能会很慢请耐心等待
<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>
</span>}>
<Col span={19}>
<Input.Group compact>
<Select value={git_type} onChange={this.switchType} style={{width: 100}}>
<Select.Option value="branch">Branch</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}
<Form.Item style={{display: 'inline-block', marginBottom: 0, width: '450px'}}>
<Input.Group compact>
<Select value={git_type} onChange={switchType} style={{width: 100}}>
<Select.Option value="branch">Branch</Select.Option>
<Select.Option value="tag">Tag</Select.Option>
</Select>
</Form.Item>
)}
<Form.Item label="备注信息">
{getFieldDecorator('desc', {initialValue: info['desc']})(
<Input placeholder="请输入备注信息"/>
)}
<Select
showSearch
style={{width: 350}}
value={extra1}
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 required label="发布目标主机" help="通过点击主机名称自由选择本次发布的主机。">
{info['app_host_ids'].map(id => (
<Tag.CheckableTag key={id} checked={host_ids.includes(id)} onChange={() => this.handleChange(id)}>
{lds.get(hostStore.idMap, `${id}.name`)}({lds.get(hostStore.idMap, `${id}.hostname`)}:{lds.get(hostStore.idMap, `${id}.port`)})
</Tag.CheckableTag>
))}
<Form.Item style={{display: 'inline-block', width: 82, textAlign: 'center', marginBottom: 0}}>
{fetching ? <LoadingOutlined style={{fontSize: 18, color: '#1890ff'}}/> :
<Button type="link" icon={<SyncOutlined/>} disabled={fetching} onClick={fetchVersions}>刷新</Button>
}
</Form.Item>
</Form>
</Modal>
)
}
}
export default Form.create()(Ext1Form)
</Form.Item>
{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
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 { Link } from 'react-router-dom';
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 styles from './index.module.css';
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)}>
<div ref={el => this.handleRef(el, item.id)}
style={{width: 135, overflow: 'hidden', textOverflow: 'ellipsis'}}>
<Icon type={item.extend === '1' ? 'ordered-list' : 'build'}
style={{marginRight: 10}}/>{item['app_name']}
{item.extend === '1' ? <OrderedListOutlined/> : <BuildOutlined/>}
<span style={{marginLeft: 8}}>{item.app_name}</span>
</div>
</Button>
</Tooltip>

View File

@ -5,9 +5,10 @@
*/
import React from '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 { Action } from "components";
import { Action, AuthButton, TableCard } from 'components';
import store from './store';
@observer
@ -25,7 +26,12 @@ class ComTable extends React.Component {
columns = [{
title: '申请标题',
dataIndex: 'name',
render: info => (
<div>
{info.type === '2' && <Tag color="#f50">R</Tag>}
{info.name}
</div>
)
}, {
title: '应用',
dataIndex: 'app_name',
@ -38,18 +44,24 @@ class ComTable extends React.Component {
if (info['app_extend'] === '1') {
const [type, ext1, ext2] = info.extra;
if (type === 'branch') {
return <React.Fragment>
<Icon type="branches"/> {ext1}#{ext2.substr(0, 6)}
</React.Fragment>
return (
<React.Fragment>
<BranchesOutlined/> {ext1}#{ext2.substr(0, 6)}
</React.Fragment>
)
} else {
return <React.Fragment>
<Icon type="tag"/> {ext1}
</React.Fragment>
return (
<React.Fragment>
<TagOutlined/> {ext1}
</React.Fragment>
)
}
} else {
return <React.Fragment>
<Icon type="build"/> {info.extra[0]}
</React.Fragment>
return (
<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>
</Popover>
} 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') {
return <Tag color="green">{info['status_alias']}</Tag>
} else if (info.status === '-3') {
return <Tag color="red">{info['status_alias']}</Tag>
} 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: '申请时间',
dataIndex: 'created_at',
sorter: (a, b) => a['created_at'].localeCompare(b['created_at'])
}, {
title: '备注',
dataIndex: 'desc',
ellipsis: true,
hide: true
}, {
title: '操作',
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 (
<Table
<TableCard
rowKey="id"
title="申请列表"
loading={store.isFetching}
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={{
showSizeChanger: true,
showLessItems: true,

View File

@ -5,8 +5,9 @@
*/
import React from 'react';
import { observer } from 'mobx-react';
import { Button, Select, DatePicker, Radio, Row, Col, Modal, Form, Input, message } from 'antd';
import { SearchForm, AuthFragment, AuthCard } from 'components';
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons';
import { Form, Select, DatePicker, Modal, Input, message } from 'antd';
import { SearchForm, AuthDiv, AuthButton, Breadcrumb } from 'components';
import SelectApp from './SelectApp';
import Ext1Form from './Ext1Form';
import Ext2Form from './Ext2Form';
@ -39,15 +40,15 @@ class Index extends React.Component {
handleBatchDel = () => {
Modal.confirm({
icon: 'exclamation-circle',
icon: <ExclamationCircleOutlined/>,
title: '批量删除发布申请',
content: (
<Form>
<Form.Item label="截止日期" help={<div>将删除截止日期<span style={{color: 'red'}}>之前</span></div>}>
<Form layout="vertical" style={{marginTop: 24}}>
<Form.Item label="截止日期 :" help={<div>将删除截止日期<span style={{color: 'red'}}>之前</span></div>}>
<DatePicker style={{width: 200}} placeholder="请输入"
onChange={val => this.setState({expire: val.format('YYYY-MM-DD')})}/>
</Form.Item>
<Form.Item label="保留记录" help="每个应用每个环境仅保留最新的N条发布申请优先级高于截止日期">
<Form.Item label="保留记录 :" help="每个应用每个环境仅保留最新的N条发布申请优先级高于截止日期">
<Input allowClear style={{width: 200}} placeholder="请输入保留个数"
onChange={e => this.setState({count: e.target.value})}/>
</Form.Item>
@ -66,7 +67,12 @@ class Index extends React.Component {
render() {
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.Item span={6} title="发布环境">
<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}/>
</SearchForm.Item>
<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>
<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/>
{store.addVisible && <SelectApp/>}
{store.ext1Visible && <Ext1Form/>}
{store.ext2Visible && <Ext2Form/>}
{store.approveVisible && <Approve/>}
</AuthCard>
</AuthDiv>
)
}
}

View File

@ -55,7 +55,7 @@ class Store {
};
updateDate = (data) => {
if (data.length === 2) {
if (data && data.length === 2) {
this.f_s_date = data[0].format('YYYY-MM-DD');
this.f_e_date = data[1].format('YYYY-MM-DD')
} 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) => (
<Form>
<Form layout="vertical" style={{marginTop: 24}}>
<Form.Item required label="授权密码" help={`用户 ${props.username} 的密码, 该密码仅做首次验证使用,不会存储该密码。`}>
<Input.Password onChange={e => setPassword(e.target.value)}/>
</Form.Item>

View File

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

View File

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