mirror of https://github.com/openspug/spug
add component AppSelector
parent
ba6c7d7c92
commit
dc5028ee3a
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||
* Copyright (c) <spug.dev@gmail.com>
|
||||
* Released under the AGPL-3.0 License.
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Modal, Button, Menu, Spin, Input, Tooltip } from 'antd';
|
||||
import { OrderedListOutlined, BuildOutlined } from '@ant-design/icons';
|
||||
import { includes, http } from 'libs';
|
||||
import styles from './index.module.less';
|
||||
import envStore from 'pages/config/environment/store';
|
||||
import lds from 'lodash';
|
||||
|
||||
export default observer(function AppSelector(props) {
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const [refs, setRefs] = useState({});
|
||||
const [env_id, setEnvId] = useState();
|
||||
const [search, setSearch] = useState();
|
||||
const [deploys, setDeploys] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setFetching(true);
|
||||
http.get('/api/app/deploy/')
|
||||
.then(res => setDeploys(res))
|
||||
.finally(() => setFetching(false))
|
||||
if (!envStore.records.length) {
|
||||
envStore.fetchRecords().then(_initEnv)
|
||||
} else {
|
||||
_initEnv()
|
||||
}
|
||||
}, [])
|
||||
|
||||
function _initEnv() {
|
||||
if (envStore.records.length) {
|
||||
setEnvId(envStore.records[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
function handleRef(el, id) {
|
||||
if (el && !refs.hasOwnProperty(id)) {
|
||||
setTimeout(() => {
|
||||
refs[id] = el.scrollWidth > el.clientWidth;
|
||||
setRefs({...refs})
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
let records = deploys.filter(x => x.env_id === Number(env_id));
|
||||
if (search) records = records.filter(x => includes(x['app_name'], search));
|
||||
if (props.filter) records = records.filter(x => props.filter(x));
|
||||
return (
|
||||
<Modal
|
||||
visible={props.visible}
|
||||
width={800}
|
||||
maskClosable={false}
|
||||
title="选择应用"
|
||||
bodyStyle={{padding: 0}}
|
||||
onCancel={props.onCancel}
|
||||
footer={null}>
|
||||
<div className={styles.appSelector}>
|
||||
<div className={styles.left}>
|
||||
<Spin spinning={envStore.isFetching}>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[String(env_id)]}
|
||||
style={{border: 'none'}}
|
||||
onSelect={({selectedKeys}) => setEnvId(selectedKeys[0])}>
|
||||
{envStore.records.map(item => <Menu.Item key={item.id}>{item.name}</Menu.Item>)}
|
||||
</Menu>
|
||||
</Spin>
|
||||
</div>
|
||||
|
||||
<div className={styles.right}>
|
||||
<Spin spinning={fetching}>
|
||||
<div className={styles.title}>
|
||||
<div>{lds.get(envStore.idMap, `${env_id}.name`)}</div>
|
||||
<Input.Search
|
||||
allowClear
|
||||
style={{width: 200}}
|
||||
placeholder="请输入快速搜应用"
|
||||
onChange={e => setSearch(e.target.value)}/>
|
||||
</div>
|
||||
{records.map(item => (
|
||||
<Tooltip key={item.id} title={refs[item.id] ? item['app_name'] : null}>
|
||||
<Button type="primary" className={styles.appBlock} onClick={() => props.onSelect(item)}>
|
||||
<div ref={el => handleRef(el, item.id)}
|
||||
style={{width: 135, overflow: 'hidden', textOverflow: 'ellipsis'}}>
|
||||
{item.extend === '1' ? <OrderedListOutlined/> : <BuildOutlined/>}
|
||||
<span style={{marginLeft: 8}}>{item.app_name}</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
{records.length === 0 &&
|
||||
<div className={styles.tips}>该环境下还没有可发布或构建的应用哦,快去<Link to="/deploy/app">应用管理</Link>创建应用发布配置吧。</div>}
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
})
|
|
@ -14,6 +14,7 @@ import ACEditor from './ACEditor';
|
|||
import Action from './Action';
|
||||
import TableCard from './TableCard';
|
||||
import Breadcrumb from './Breadcrumb';
|
||||
import AppSelector from './AppSelector';
|
||||
|
||||
export {
|
||||
StatisticsCard,
|
||||
|
@ -27,4 +28,5 @@ export {
|
|||
Action,
|
||||
TableCard,
|
||||
Breadcrumb,
|
||||
AppSelector,
|
||||
}
|
||||
|
|
|
@ -143,3 +143,44 @@
|
|||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.appSelector {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
padding: 16px 0;
|
||||
min-height: 500px;
|
||||
|
||||
.left {
|
||||
flex: 2;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 7;
|
||||
padding: 8px 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.appBlock {
|
||||
margin-top: 20px;
|
||||
width: 165px;
|
||||
height: 60px;
|
||||
font-size: 18px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 32px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,15 @@ export function hasHostPermission(id) {
|
|||
return isSuper || hostPerms.includes(id)
|
||||
}
|
||||
|
||||
export function includes(s, key) {
|
||||
key = key.toLowerCase();
|
||||
if (s) {
|
||||
return s.toLowerCase().includes(key)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 清理输入的命令中包含的\r符号
|
||||
export function cleanCommand(text) {
|
||||
return text ? text.replace(/\r\n/g, '\n') : ''
|
||||
|
|
|
@ -1,120 +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 { Link } from 'react-router-dom';
|
||||
import { observer } from 'mobx-react';
|
||||
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';
|
||||
import lds from 'lodash';
|
||||
|
||||
@observer
|
||||
class SelectApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
env_id: 0,
|
||||
search: ''
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
store.loadDeploys();
|
||||
if (envStore.records.length === 0) {
|
||||
envStore.fetchRecords().then(this._initEnv)
|
||||
} else {
|
||||
this._initEnv()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
store.refs = {}
|
||||
}
|
||||
|
||||
_initEnv = () => {
|
||||
if (envStore.records.length) {
|
||||
this.setState({env_id: envStore.records[0].id})
|
||||
}
|
||||
};
|
||||
|
||||
handleClick = (deploy) => {
|
||||
store.record = {deploy_id: deploy.id, app_host_ids: deploy.host_ids};
|
||||
if (deploy.extend === '1') {
|
||||
store.ext1Visible = true
|
||||
} else {
|
||||
store.ext2Visible = true
|
||||
}
|
||||
store.addVisible = false
|
||||
};
|
||||
|
||||
handleRef = (el, id) => {
|
||||
if (el && !store.refs.hasOwnProperty(id)) {
|
||||
setTimeout(() => store.refs[id] = el.scrollWidth > el.clientWidth, 200)
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {env_id} = this.state;
|
||||
let records = store.deploys.filter(x => x.env_id === Number(env_id));
|
||||
if (this.state.search) {
|
||||
records = records.filter(x => x['app_name'].toLowerCase().includes(this.state.search.toLowerCase()))
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
visible
|
||||
width={800}
|
||||
maskClosable={false}
|
||||
title="选择应用"
|
||||
bodyStyle={{padding: 0}}
|
||||
onCancel={() => store.addVisible = false}
|
||||
footer={null}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.left}>
|
||||
<Spin spinning={envStore.isFetching}>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[String(env_id)]}
|
||||
style={{border: 'none'}}
|
||||
onSelect={({selectedKeys}) => this.setState({env_id: selectedKeys[0]})}>
|
||||
{envStore.records.map(item => <Menu.Item key={item.id}>{item.name}</Menu.Item>)}
|
||||
</Menu>
|
||||
</Spin>
|
||||
</div>
|
||||
|
||||
<div className={styles.right}>
|
||||
<Spin spinning={store.isLoading}>
|
||||
<div className={styles.title}>
|
||||
<div>{lds.get(envStore.idMap, `${env_id}.name`)}</div>
|
||||
<Input.Search
|
||||
allowClear
|
||||
style={{width: 200}}
|
||||
placeholder="请输入快速搜应用"
|
||||
onChange={e => this.setState({search: e.target.value})}/>
|
||||
</div>
|
||||
{records.map(item => (
|
||||
<Tooltip key={item.id} title={store.refs[item.id] ? item['app_name'] : null}>
|
||||
<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'}}>
|
||||
{item.extend === '1' ? <OrderedListOutlined/> : <BuildOutlined/>}
|
||||
<span style={{marginLeft: 8}}>{item.app_name}</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
{records.length === 0 &&
|
||||
<div className={styles.tips}>该环境下还没有可发布的应用哦,快去<Link to="/deploy/app">应用管理</Link>创建应用发布配置吧。</div>}
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectApp
|
|
@ -7,8 +7,7 @@ import React from 'react';
|
|||
import { observer } from 'mobx-react';
|
||||
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 { SearchForm, AuthDiv, AuthButton, Breadcrumb, AppSelector } from 'components';
|
||||
import Ext1Form from './Ext1Form';
|
||||
import Ext2Form from './Ext2Form';
|
||||
import Approve from './Approve';
|
||||
|
@ -102,7 +101,10 @@ class Index extends React.Component {
|
|||
</SearchForm.Item>
|
||||
</SearchForm>
|
||||
<ComTable/>
|
||||
{store.addVisible && <SelectApp/>}
|
||||
<AppSelector
|
||||
visible={store.addVisible}
|
||||
onCancel={() => store.addVisible = false}
|
||||
onSelect={store.confirmAdd}/>
|
||||
{store.ext1Visible && <Ext1Form/>}
|
||||
{store.ext2Visible && <Ext2Form/>}
|
||||
{store.approveVisible && <Approve/>}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
padding: 16px 0;
|
||||
min-height: 500px;
|
||||
}
|
||||
.left {
|
||||
flex: 2;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
}
|
||||
.right {
|
||||
flex: 7;
|
||||
padding: 8px 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
color: rgba(0, 0, 0, .85);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.appBlock {
|
||||
margin-top: 20px;
|
||||
width: 165px;
|
||||
height: 60px;
|
||||
font-size: 18px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 32px;
|
||||
color: #888;
|
||||
}
|
|
@ -8,12 +8,8 @@ import http from 'libs/http';
|
|||
|
||||
class Store {
|
||||
@observable records = [];
|
||||
@observable deploys = [];
|
||||
@observable types = [];
|
||||
@observable record = {};
|
||||
@observable refs = {};
|
||||
@observable counter = {};
|
||||
@observable isLoading = false;
|
||||
@observable isFetching = false;
|
||||
@observable addVisible = false;
|
||||
@observable ext1Visible = false;
|
||||
|
@ -64,6 +60,16 @@ class Store {
|
|||
}
|
||||
};
|
||||
|
||||
confirmAdd = (deploy) => {
|
||||
this.record = {deploy_id: deploy.id, app_host_ids: deploy.host_ids};
|
||||
if (deploy.extend === '1') {
|
||||
this.ext1Visible = true
|
||||
} else {
|
||||
this.ext2Visible = true
|
||||
}
|
||||
this.addVisible = false
|
||||
};
|
||||
|
||||
showForm = (info) => {
|
||||
this.record = info;
|
||||
if (info['app_extend'] === '1') {
|
||||
|
|
Loading…
Reference in New Issue