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 Action from './Action';
|
||||||
import TableCard from './TableCard';
|
import TableCard from './TableCard';
|
||||||
import Breadcrumb from './Breadcrumb';
|
import Breadcrumb from './Breadcrumb';
|
||||||
|
import AppSelector from './AppSelector';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
StatisticsCard,
|
StatisticsCard,
|
||||||
|
@ -27,4 +28,5 @@ export {
|
||||||
Action,
|
Action,
|
||||||
TableCard,
|
TableCard,
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
|
AppSelector,
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,3 +143,44 @@
|
||||||
line-height: 50px;
|
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)
|
return isSuper || hostPerms.includes(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function includes(s, key) {
|
||||||
|
key = key.toLowerCase();
|
||||||
|
if (s) {
|
||||||
|
return s.toLowerCase().includes(key)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 清理输入的命令中包含的\r符号
|
// 清理输入的命令中包含的\r符号
|
||||||
export function cleanCommand(text) {
|
export function cleanCommand(text) {
|
||||||
return text ? text.replace(/\r\n/g, '\n') : ''
|
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 { observer } from 'mobx-react';
|
||||||
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { Form, Select, DatePicker, Modal, Input, message } from 'antd';
|
import { Form, Select, DatePicker, Modal, Input, message } from 'antd';
|
||||||
import { SearchForm, AuthDiv, AuthButton, Breadcrumb } from 'components';
|
import { SearchForm, AuthDiv, AuthButton, Breadcrumb, AppSelector } from 'components';
|
||||||
import SelectApp from './SelectApp';
|
|
||||||
import Ext1Form from './Ext1Form';
|
import Ext1Form from './Ext1Form';
|
||||||
import Ext2Form from './Ext2Form';
|
import Ext2Form from './Ext2Form';
|
||||||
import Approve from './Approve';
|
import Approve from './Approve';
|
||||||
|
@ -102,7 +101,10 @@ class Index extends React.Component {
|
||||||
</SearchForm.Item>
|
</SearchForm.Item>
|
||||||
</SearchForm>
|
</SearchForm>
|
||||||
<ComTable/>
|
<ComTable/>
|
||||||
{store.addVisible && <SelectApp/>}
|
<AppSelector
|
||||||
|
visible={store.addVisible}
|
||||||
|
onCancel={() => store.addVisible = false}
|
||||||
|
onSelect={store.confirmAdd}/>
|
||||||
{store.ext1Visible && <Ext1Form/>}
|
{store.ext1Visible && <Ext1Form/>}
|
||||||
{store.ext2Visible && <Ext2Form/>}
|
{store.ext2Visible && <Ext2Form/>}
|
||||||
{store.approveVisible && <Approve/>}
|
{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 {
|
class Store {
|
||||||
@observable records = [];
|
@observable records = [];
|
||||||
@observable deploys = [];
|
|
||||||
@observable types = [];
|
|
||||||
@observable record = {};
|
@observable record = {};
|
||||||
@observable refs = {};
|
|
||||||
@observable counter = {};
|
@observable counter = {};
|
||||||
@observable isLoading = false;
|
|
||||||
@observable isFetching = false;
|
@observable isFetching = false;
|
||||||
@observable addVisible = false;
|
@observable addVisible = false;
|
||||||
@observable ext1Visible = 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) => {
|
showForm = (info) => {
|
||||||
this.record = info;
|
this.record = info;
|
||||||
if (info['app_extend'] === '1') {
|
if (info['app_extend'] === '1') {
|
||||||
|
|
Loading…
Reference in New Issue