mirror of https://github.com/openspug/spug
U 优化主机分组支持全局搜索
parent
0b9ab1e379
commit
7872d8cb85
|
@ -5,7 +5,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.3.0",
|
"@ant-design/icons": "^4.3.0",
|
||||||
"ace-builds": "^1.4.13",
|
"ace-builds": "^1.4.13",
|
||||||
"antd": "^4.19.2",
|
"antd": "4.21.4",
|
||||||
"axios": "^0.21.0",
|
"axios": "^0.21.0",
|
||||||
"bizcharts": "^3.5.9",
|
"bizcharts": "^3.5.9",
|
||||||
"history": "^4.10.1",
|
"history": "^4.10.1",
|
||||||
|
@ -45,9 +45,9 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-decorators": "^7.10.5",
|
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||||
"customize-cra": "^1.0.0",
|
"customize-cra": "^1.0.0",
|
||||||
|
"http-proxy-middleware": "0.19.2",
|
||||||
"less": "^3.12.2",
|
"less": "^3.12.2",
|
||||||
"less-loader": "^7.1.0",
|
"less-loader": "^7.1.0",
|
||||||
"react-app-rewired": "^2.1.6",
|
"react-app-rewired": "^2.1.6"
|
||||||
"http-proxy-middleware": "0.19.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,15 +39,29 @@ export function clsNames(...args) {
|
||||||
return args.filter(x => x).join(' ')
|
return args.filter(x => x).join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function includes(s, key) {
|
function isInclude(s, keys) {
|
||||||
key = key.toLowerCase();
|
if (!s) return false
|
||||||
if (Array.isArray(s)) {
|
if (Array.isArray(keys)) {
|
||||||
for (let i of s) {
|
for (let k of keys) {
|
||||||
if (i && i.toLowerCase().includes(key)) return true
|
k = k.toLowerCase()
|
||||||
|
if (s.toLowerCase().includes(k)) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
return s && s.toLowerCase().includes(key)
|
let k = keys.toLowerCase()
|
||||||
|
return s.toLowerCase().includes(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字符串包含判断
|
||||||
|
export function includes(s, keys) {
|
||||||
|
if (Array.isArray(s)) {
|
||||||
|
for (let i of s) {
|
||||||
|
if (isInclude(i, keys)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return isInclude(s, keys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { observer } from 'mobx-react';
|
||||||
import { Modal, Form, Input, Select, DatePicker, Button, message } from 'antd';
|
import { Modal, Form, Input, Select, DatePicker, Button, message } from 'antd';
|
||||||
import { LoadingOutlined, SyncOutlined } from '@ant-design/icons';
|
import { LoadingOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
import HostSelector from './HostSelector';
|
import HostSelector from './HostSelector';
|
||||||
import hostStore from 'pages/host/store';
|
|
||||||
import { http, history } from 'libs';
|
import { http, history } from 'libs';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import lds from 'lodash';
|
import lds from 'lodash';
|
||||||
|
@ -44,7 +43,6 @@ export default observer(function () {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const {app_host_ids, host_ids} = store.record;
|
const {app_host_ids, host_ids} = store.record;
|
||||||
setHostIds(lds.clone(host_ids || app_host_ids));
|
setHostIds(lds.clone(host_ids || app_host_ids));
|
||||||
if (!hostStore.records || hostStore.records.length === 0) hostStore.fetchRecords()
|
|
||||||
fetchVersions()
|
fetchVersions()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -7,7 +7,6 @@ import React, { useState, useEffect } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
import { Modal, Form, Input, Upload, DatePicker, message, Button } from 'antd';
|
import { Modal, Form, Input, Upload, DatePicker, message, Button } from 'antd';
|
||||||
import hostStore from 'pages/host/store';
|
|
||||||
import HostSelector from './HostSelector';
|
import HostSelector from './HostSelector';
|
||||||
import { http, clsNames, X_TOKEN } from 'libs';
|
import { http, clsNames, X_TOKEN } from 'libs';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
|
@ -26,7 +25,6 @@ export default observer(function () {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const {app_host_ids, host_ids, extra} = store.record;
|
const {app_host_ids, host_ids, extra} = store.record;
|
||||||
setHostIds(lds.clone(host_ids || app_host_ids));
|
setHostIds(lds.clone(host_ids || app_host_ids));
|
||||||
if (!hostStore.records || hostStore.records.length === 0) hostStore.fetchRecords();
|
|
||||||
if (store.record.extra) setFileList([{...extra, uid: '0'}])
|
if (store.record.extra) setFileList([{...extra, uid: '0'}])
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Modal, Table, Button, Alert } from 'antd';
|
import { Modal, Table, Button, Alert } from 'antd';
|
||||||
import hostStore from 'pages/host/store';
|
import hostStore from 'pages/host/store';
|
||||||
|
@ -7,6 +7,10 @@ import lds from 'lodash';
|
||||||
export default observer(function (props) {
|
export default observer(function (props) {
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState(props.host_ids || []);
|
const [selectedRowKeys, setSelectedRowKeys] = useState(props.host_ids || []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
hostStore.initial()
|
||||||
|
}, [])
|
||||||
|
|
||||||
function handleClickRow(record) {
|
function handleClickRow(record) {
|
||||||
const index = selectedRowKeys.indexOf(record.id);
|
const index = selectedRowKeys.indexOf(record.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import React, { useState, useEffect } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Modal, Form, Input, Select, Button, message } from 'antd';
|
import { Modal, Form, Input, Select, Button, message } from 'antd';
|
||||||
import HostSelector from './HostSelector';
|
import HostSelector from './HostSelector';
|
||||||
import hostStore from 'pages/host/store';
|
|
||||||
import { http, includes } from 'libs';
|
import { http, includes } from 'libs';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import lds from 'lodash';
|
import lds from 'lodash';
|
||||||
|
@ -22,7 +21,6 @@ export default observer(function () {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const {app_host_ids, host_ids} = store.record;
|
const {app_host_ids, host_ids} = store.record;
|
||||||
setHostIds(lds.clone(host_ids || app_host_ids));
|
setHostIds(lds.clone(host_ids || app_host_ids));
|
||||||
if (!hostStore.records || hostStore.records.length === 0) hostStore.fetchRecords()
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { Input, Card, Tree, Dropdown, Menu, Switch, Tooltip, Spin, Modal } from
|
||||||
import {
|
import {
|
||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
FolderAddOutlined,
|
FolderAddOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
CopyOutlined,
|
CopyOutlined,
|
||||||
|
@ -24,11 +25,12 @@ import store from './store';
|
||||||
import lds from 'lodash';
|
import lds from 'lodash';
|
||||||
|
|
||||||
export default observer(function () {
|
export default observer(function () {
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
const [loading, setLoading] = useState();
|
const [loading, setLoading] = useState();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [draggable, setDraggable] = useState(false);
|
const [draggable, setDraggable] = useState(false);
|
||||||
const [action, setAction] = useState('');
|
const [action, setAction] = useState('');
|
||||||
const [expands, setExpands] = useState();
|
const [expands, setExpands] = useState([]);
|
||||||
const [bakTreeData, setBakTreeData] = useState();
|
const [bakTreeData, setBakTreeData] = useState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -36,10 +38,13 @@ export default observer(function () {
|
||||||
}, [loading])
|
}, [loading])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const length = store.treeData.length
|
if (!isReady) {
|
||||||
if (length > 0 && length < 5 && expands === undefined) {
|
const length = store.treeData.length
|
||||||
const tmp = store.treeData.filter(x => x.children.length)
|
if (length > 0 && length < 5) {
|
||||||
setExpands(tmp.map(x => x.key))
|
const tmp = store.treeData.filter(x => x.children.length)
|
||||||
|
setExpands(tmp.map(x => x.key))
|
||||||
|
setIsReady(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [store.treeData])
|
}, [store.treeData])
|
||||||
|
@ -50,10 +55,10 @@ export default observer(function () {
|
||||||
<Menu.Item key="1" icon={<FolderAddOutlined/>} onClick={handleAdd}>新建子分组</Menu.Item>
|
<Menu.Item key="1" icon={<FolderAddOutlined/>} onClick={handleAdd}>新建子分组</Menu.Item>
|
||||||
<Menu.Item key="2" icon={<EditOutlined/>} onClick={() => setAction('edit')}>重命名</Menu.Item>
|
<Menu.Item key="2" icon={<EditOutlined/>} onClick={() => setAction('edit')}>重命名</Menu.Item>
|
||||||
<Menu.Divider/>
|
<Menu.Divider/>
|
||||||
<Menu.Item key="3" icon={<CopyOutlined/>} onClick={() => store.showSelector(true)}>添加至分组</Menu.Item>
|
<Menu.Item key="3" icon={<CopyOutlined/>} onClick={() => store.showSelector(true)}>添加主机</Menu.Item>
|
||||||
<Menu.Item key="4" icon={<ScissorOutlined/>} onClick={() => store.showSelector(false)}>移动至分组</Menu.Item>
|
<Menu.Item key="4" icon={<ScissorOutlined/>} onClick={() => store.showSelector(false)}>移动主机</Menu.Item>
|
||||||
<Menu.Divider/>
|
|
||||||
<Menu.Item key="5" icon={<CloseOutlined/>} danger onClick={handleRemoveHosts}>删除主机</Menu.Item>
|
<Menu.Item key="5" icon={<CloseOutlined/>} danger onClick={handleRemoveHosts}>删除主机</Menu.Item>
|
||||||
|
<Menu.Divider/>
|
||||||
<Menu.Item key="6" icon={<DeleteOutlined/>} danger onClick={handleRemove}>删除此分组</Menu.Item>
|
<Menu.Item key="6" icon={<DeleteOutlined/>} danger onClick={handleRemove}>删除此分组</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
|
@ -66,7 +71,7 @@ export default observer(function () {
|
||||||
.then(() => setAction(''))
|
.then(() => setAction(''))
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
} else {
|
} else {
|
||||||
if (store.group.key === 0) store.treeData = bakTreeData
|
if (store.group.key === 0) store.rawTreeData = bakTreeData
|
||||||
setAction('')
|
setAction('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,9 +80,9 @@ export default observer(function () {
|
||||||
const group = store.group;
|
const group = store.group;
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '操作确认',
|
title: '操作确认',
|
||||||
content: `批量删除【${group.title}】分组内的 ${group.all_host_ids.length} 个主机?`,
|
content: `批量删除【${group.title}】分组内的 ${store.counter[group.key].size} 个主机?`,
|
||||||
onOk: () => http.delete('/api/host/', {params: {group_id: group.key}})
|
onOk: () => http.delete('/api/host/', {params: {group_id: group.key}})
|
||||||
.then(store.initial)
|
.then(store.fetchRecords)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,24 +97,34 @@ export default observer(function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddRoot() {
|
function handleAddRoot() {
|
||||||
setBakTreeData(lds.cloneDeep(store.treeData));
|
setBakTreeData(lds.cloneDeep(store.rawTreeData));
|
||||||
const current = {key: 0, parent_id: 0, title: ''};
|
const current = {key: 0, parent_id: 0, title: '', children: []};
|
||||||
store.treeData.unshift(current);
|
store.rawTreeData.unshift(current);
|
||||||
store.treeData = lds.cloneDeep(store.treeData);
|
store.rawTreeData = lds.cloneDeep(store.rawTreeData);
|
||||||
store.group = current;
|
store.group = current;
|
||||||
setAction('edit')
|
setAction('edit')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
setBakTreeData(lds.cloneDeep(store.treeData));
|
setBakTreeData(lds.cloneDeep(store.rawTreeData));
|
||||||
const current = {key: 0, parent_id: store.group.key, title: ''};
|
const current = {key: 0, parent_id: store.group.key, title: '', children: []};
|
||||||
store.group.children.unshift(current);
|
const node = _find_node(store.rawTreeData, store.group.key)
|
||||||
store.treeData = lds.cloneDeep(store.treeData);
|
node.children.unshift(current)
|
||||||
|
store.rawTreeData = lds.cloneDeep(store.rawTreeData);
|
||||||
if (!expands.includes(store.group.key)) setExpands([store.group.key, ...expands]);
|
if (!expands.includes(store.group.key)) setExpands([store.group.key, ...expands]);
|
||||||
store.group = current;
|
store.group = current;
|
||||||
setAction('edit')
|
setAction('edit')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _find_node(list, key) {
|
||||||
|
let node = lds.find(list, {key})
|
||||||
|
if (node) return node
|
||||||
|
for (let item of list) {
|
||||||
|
node = _find_node(item.children, key)
|
||||||
|
if (node) return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleDrag(v) {
|
function handleDrag(v) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const pos = v.node.pos.split('-');
|
const pos = v.node.pos.split('-');
|
||||||
|
@ -138,6 +153,7 @@ export default observer(function () {
|
||||||
size="small"
|
size="small"
|
||||||
style={{width: 'calc(100% - 24px)'}}
|
style={{width: 'calc(100% - 24px)'}}
|
||||||
defaultValue={nodeData.title}
|
defaultValue={nodeData.title}
|
||||||
|
placeholder="请输入"
|
||||||
suffix={loading ? <LoadingOutlined/> : <span/>}
|
suffix={loading ? <LoadingOutlined/> : <span/>}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
onBlur={handleSubmit}
|
onBlur={handleSubmit}
|
||||||
|
@ -146,9 +162,13 @@ export default observer(function () {
|
||||||
} else if (action === 'del' && nodeData.key === store.group.key) {
|
} else if (action === 'del' && nodeData.key === store.group.key) {
|
||||||
return <LoadingOutlined style={{marginLeft: '4px'}}/>
|
return <LoadingOutlined style={{marginLeft: '4px'}}/>
|
||||||
} else {
|
} else {
|
||||||
const extend = nodeData.all_host_ids && nodeData.all_host_ids.length ? `(${nodeData.all_host_ids.length})` : null
|
const length = store.counter[nodeData.key]?.size
|
||||||
return (
|
return (
|
||||||
<span style={{lineHeight: '24px'}}>{nodeData.title}{extend}</span>
|
<div className={styles.treeNode}>
|
||||||
|
{expands.includes(nodeData.key) ? <FolderOpenOutlined/> : <FolderOutlined/>}
|
||||||
|
<div className={styles.title}>{nodeData.title}</div>
|
||||||
|
{length ? <div className={styles.number}>{length}</div> : null}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +185,7 @@ export default observer(function () {
|
||||||
onChange={setDraggable}
|
onChange={setDraggable}
|
||||||
checkedChildren="排版"
|
checkedChildren="排版"
|
||||||
unCheckedChildren="浏览"/>
|
unCheckedChildren="浏览"/>
|
||||||
<Tooltip title="排版模式下,可通过拖拽分组实现快速排序。">
|
<Tooltip title="排版模式下,可通过拖拽分组实现快速排序,右键点击分组进行分组管理。">
|
||||||
<QuestionCircleOutlined style={{marginLeft: 8, color: '#999'}}/>
|
<QuestionCircleOutlined style={{marginLeft: 8, color: '#999'}}/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</AuthFragment>)}>
|
</AuthFragment>)}>
|
||||||
|
@ -176,6 +196,7 @@ export default observer(function () {
|
||||||
trigger={['contextMenu']}
|
trigger={['contextMenu']}
|
||||||
onVisibleChange={v => v || setVisible(v)}>
|
onVisibleChange={v => v || setVisible(v)}>
|
||||||
<Tree.DirectoryTree
|
<Tree.DirectoryTree
|
||||||
|
showIcon={false}
|
||||||
autoExpandParent
|
autoExpandParent
|
||||||
expandAction="doubleClick"
|
expandAction="doubleClick"
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
|
|
|
@ -6,67 +6,69 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Modal, Row, Col, Tree, Table, Button, Space, Input } from 'antd';
|
import { Modal, Row, Col, Tree, Table, Button, Space, Input } from 'antd';
|
||||||
import { includes } from 'libs';
|
import { FolderOpenOutlined, FolderOutlined } from '@ant-design/icons';
|
||||||
import store from './store';
|
import hStore from './store';
|
||||||
|
import store from './store2';
|
||||||
import styles from './index.module.less';
|
import styles from './index.module.less';
|
||||||
|
|
||||||
|
|
||||||
export default observer(function (props) {
|
export default observer(function (props) {
|
||||||
|
const [isReady, setIsReady] = useState(false)
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [group, setGroup] = useState({});
|
|
||||||
const [dataSource, setDataSource] = useState([]);
|
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
||||||
const [fKey, setFKey] = useState();
|
const [expands, setExpands] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!store.treeData.length) {
|
store.onlySelf = props.onlySelf;
|
||||||
store.initial()
|
hStore.initial().then(() => {
|
||||||
.then(() => setGroup(store.treeData[0] || {}))
|
store.rawRecords = hStore.rawRecords;
|
||||||
} else {
|
store.rawTreeData = hStore.rawTreeData;
|
||||||
setGroup(store.treeData[0] || {})
|
store.group = store.treeData[0]
|
||||||
}
|
})
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isReady) {
|
||||||
|
const length = store.treeData.length
|
||||||
|
if (length > 0 && length < 5) {
|
||||||
|
const tmp = store.treeData.filter(x => x.children.length)
|
||||||
|
setExpands(tmp.map(x => x.key))
|
||||||
|
setIsReady(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [store.treeData])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedRowKeys(props.selectedRowKeys || [])
|
setSelectedRowKeys(props.selectedRowKeys || [])
|
||||||
}, [props.selectedRowKeys])
|
}, [props.selectedRowKeys])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let records = store.records;
|
if (props.oneGroup) {
|
||||||
if (group.key) records = records.filter(x => group.self_host_ids.includes(x.id));
|
setSelectedRowKeys([])
|
||||||
if (fKey) records = records.filter(x => includes(x.name, fKey) || includes(x.hostname, fKey));
|
}
|
||||||
setDataSource(records)
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [group, fKey])
|
}, [store.group])
|
||||||
|
|
||||||
function treeRender(nodeData) {
|
|
||||||
return (
|
|
||||||
<span style={{lineHeight: '24px'}}>
|
|
||||||
{nodeData.title}{nodeData.self_host_ids && nodeData.self_host_ids.length ? `(${nodeData.self_host_ids.length})` : null}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClickRow(record) {
|
function handleClickRow(record) {
|
||||||
let tmp = [...selectedRowKeys]
|
let tmp = new Set(selectedRowKeys)
|
||||||
const index = tmp.indexOf(record.id);
|
if (!tmp.delete(record.id)) {
|
||||||
if (index !== -1) {
|
if (props.onlyOne) tmp.clear()
|
||||||
tmp.splice(index, 1)
|
tmp.add(record.id)
|
||||||
} else if (props.onlyOne) {
|
|
||||||
tmp = [record.id]
|
|
||||||
} else {
|
|
||||||
tmp.push(record.id)
|
|
||||||
}
|
}
|
||||||
setSelectedRowKeys(tmp)
|
setSelectedRowKeys([...tmp])
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
if (props.onOk) {
|
if (props.onOk) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let res
|
let res
|
||||||
const selectedRows = store.records.filter(x => selectedRowKeys.includes(x.id))
|
const selectedRows = store.rawRecords.filter(x => selectedRowKeys.includes(x.id))
|
||||||
if (props.onlyOne) {
|
if (props.onlyOne) {
|
||||||
res = props.onOk(group, selectedRowKeys[0], selectedRows[0])
|
res = props.onOk(store.group, selectedRowKeys[0], selectedRows[0])
|
||||||
} else {
|
} else {
|
||||||
res = props.onOk(group, selectedRowKeys, selectedRows);
|
res = props.onOk(store.group, selectedRowKeys, selectedRows);
|
||||||
}
|
}
|
||||||
if (res && res.then) {
|
if (res && res.then) {
|
||||||
res.then(props.onCancel, () => setLoading(false))
|
res.then(props.onCancel, () => setLoading(false))
|
||||||
|
@ -77,9 +79,33 @@ export default observer(function (props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleChangeGrp(node) {
|
function handleExpand(keys, {_, node}) {
|
||||||
setGroup(node);
|
if (node.children.length > 0) {
|
||||||
if (props.oneGroup) setSelectedRowKeys([])
|
setExpands(keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectAll(selected) {
|
||||||
|
let tmp = new Set(selectedRowKeys)
|
||||||
|
for (let item of store.dataSource) {
|
||||||
|
if (selected) {
|
||||||
|
tmp.add(item.id)
|
||||||
|
} else {
|
||||||
|
tmp.delete(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelectedRowKeys([...tmp])
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeRender(nodeData) {
|
||||||
|
const length = store.counter[nodeData.key]?.size
|
||||||
|
return (
|
||||||
|
<div className={styles.treeNode}>
|
||||||
|
{expands.includes(nodeData.key) ? <FolderOpenOutlined/> : <FolderOutlined/>}
|
||||||
|
<div className={styles.title}>{nodeData.title}</div>
|
||||||
|
{length ? <div className={styles.number}>{length}</div> : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -95,25 +121,29 @@ export default observer(function (props) {
|
||||||
<Row gutter={12}>
|
<Row gutter={12}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Tree.DirectoryTree
|
<Tree.DirectoryTree
|
||||||
defaultExpandAll={store.treeData.length > 0 && store.treeData.length < 5}
|
showIcon={false}
|
||||||
|
autoExpandParent
|
||||||
expandAction="doubleClick"
|
expandAction="doubleClick"
|
||||||
selectedKeys={[group.key]}
|
selectedKeys={[store.group.key]}
|
||||||
|
expandedKeys={expands}
|
||||||
treeData={store.treeData}
|
treeData={store.treeData}
|
||||||
titleRender={treeRender}
|
titleRender={treeRender}
|
||||||
onSelect={(_, {node}) => handleChangeGrp(node)}
|
onExpand={handleExpand}
|
||||||
|
onSelect={(_, {node}) => store.group = node}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={18}>
|
<Col span={18}>
|
||||||
<div style={{display: 'flex', justifyContent: 'space-between', marginBottom: 12}}>
|
<div style={{display: 'flex', justifyContent: 'space-between', marginBottom: 12}}>
|
||||||
<Input allowClear style={{width: 260}} placeholder="输入检索" onChange={e => setFKey(e.target.value)}/>
|
<Input allowClear style={{width: 260}} placeholder="输入名称/IP检索"
|
||||||
|
onChange={e => store.f_word = e.target.value}/>
|
||||||
<Space hidden={selectedRowKeys.length === 0}>
|
<Space hidden={selectedRowKeys.length === 0}>
|
||||||
<div>已选择 {selectedRowKeys.length} 台主机</div>
|
<div>已选择 {selectedRowKeys.length} 台主机</div>
|
||||||
<Button type="link" onClick={() => setSelectedRowKeys([])}>取消选择</Button>
|
<Button type="link" style={{paddingRight: 0}} onClick={() => setSelectedRowKeys([])}>取消选择</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={dataSource}
|
dataSource={store.dataSource}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
scroll={{y: 480}}
|
scroll={{y: 480}}
|
||||||
onRow={record => {
|
onRow={record => {
|
||||||
|
@ -123,11 +153,12 @@ export default observer(function (props) {
|
||||||
}}
|
}}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
|
hideSelectAll: props.onlyOne,
|
||||||
onSelect: handleClickRow,
|
onSelect: handleClickRow,
|
||||||
onSelectAll: (_, __, changeRows) => changeRows.map(x => handleClickRow(x))
|
onSelectAll: handleSelectAll
|
||||||
}}>
|
}}>
|
||||||
<Table.Column title="主机名称" dataIndex="name" sorter={(a, b) => a.name.localeCompare(b.name)}/>
|
<Table.Column title="主机名称" dataIndex="name" sorter={(a, b) => a.name.localeCompare(b.name)}/>
|
||||||
<Table.Column title="连接地址" dataIndex="hostname" sorter={(a, b) => a.name.localeCompare(b.name)}/>
|
<Table.Column title="IP地址" dataIndex="hostname" sorter={(a, b) => a.name.localeCompare(b.name)}/>
|
||||||
<Table.Column hide ellipsis title="备注信息" dataIndex="desc"/>
|
<Table.Column hide ellipsis title="备注信息" dataIndex="desc"/>
|
||||||
</Table>
|
</Table>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -40,7 +40,7 @@ function ComTable() {
|
||||||
function IpAddress(props) {
|
function IpAddress(props) {
|
||||||
if (props.ip && props.ip.length > 0) {
|
if (props.ip && props.ip.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div>{props.ip[0]}<span style={{color: '#999'}}>({props.isPublic ? '公' : '私有'})</span></div>
|
<div>{props.ip[0]}<span style={{color: '#999'}}>({props.isPublic ? '公' : '私'})</span></div>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
|
@ -51,7 +51,8 @@ function ComTable() {
|
||||||
<TableCard
|
<TableCard
|
||||||
tKey="hi"
|
tKey="hi"
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
title={<Input placeholder="输入名称/IP检索" style={{maxWidth: 250}} onChange={e => store.f_word = e.target.value}/>}
|
title={<Input allowClear value={store.f_word} placeholder="输入名称/IP检索" style={{maxWidth: 250}}
|
||||||
|
onChange={e => store.f_word = e.target.value}/>}
|
||||||
loading={store.isFetching}
|
loading={store.isFetching}
|
||||||
dataSource={store.dataSource}
|
dataSource={store.dataSource}
|
||||||
onReload={store.fetchRecords}
|
onReload={store.fetchRecords}
|
||||||
|
|
|
@ -50,7 +50,11 @@ export default observer(function () {
|
||||||
{store.cloudImport && <CloudImport/>}
|
{store.cloudImport && <CloudImport/>}
|
||||||
{store.syncVisible && <BatchSync/>}
|
{store.syncVisible && <BatchSync/>}
|
||||||
{store.selectorVisible &&
|
{store.selectorVisible &&
|
||||||
<Selector oneGroup={!store.addByCopy} onCancel={() => store.selectorVisible = false} onOk={store.updateGroup}/>}
|
<Selector
|
||||||
|
onlySelf={!store.addByCopy}
|
||||||
|
onCancel={() => store.selectorVisible = false}
|
||||||
|
onOk={store.updateGroup}
|
||||||
|
/>}
|
||||||
</AuthDiv>
|
</AuthDiv>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,8 +26,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector :global(.ant-modal-footer) {
|
.selector {
|
||||||
border-top: none
|
:global(.ant-modal-footer) {
|
||||||
|
border-top: none
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.formAddress1 {
|
.formAddress1 {
|
||||||
|
@ -62,11 +64,27 @@
|
||||||
|
|
||||||
.group {
|
.group {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.ant-tree-node-content-wrapper) {
|
.treeNode {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-left: 8px;
|
||||||
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
word-break: break-all;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
width: 30px;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,13 @@
|
||||||
* 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 { observable, computed } from 'mobx';
|
import { observable, computed, toJS } from 'mobx';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { http, includes } from 'libs';
|
import { http, includes } from 'libs';
|
||||||
import lds from 'lodash';
|
|
||||||
|
|
||||||
class Store {
|
class Store {
|
||||||
counter = {};
|
@observable rawTreeData = [];
|
||||||
@observable records = null;
|
@observable rawRecords = [];
|
||||||
@observable treeData = [];
|
|
||||||
@observable groups = {};
|
@observable groups = {};
|
||||||
@observable group = {};
|
@observable group = {};
|
||||||
@observable record = {};
|
@observable record = {};
|
||||||
|
@ -29,24 +27,61 @@ class Store {
|
||||||
@observable f_word;
|
@observable f_word;
|
||||||
@observable f_status = '';
|
@observable f_status = '';
|
||||||
|
|
||||||
|
@computed get records() {
|
||||||
|
let records = this.rawRecords;
|
||||||
|
if (this.f_word) {
|
||||||
|
records = records.filter(x => {
|
||||||
|
if (includes(x.name, this.f_word)) return true
|
||||||
|
if (x.public_ip_address && includes(x.public_ip_address[0], this.f_word)) return true
|
||||||
|
return !!(x.private_ip_address && includes(x.private_ip_address[0], this.f_word));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
@computed get dataSource() {
|
@computed get dataSource() {
|
||||||
let records = [];
|
let records = [];
|
||||||
if (this.group.all_host_ids) records = this.records ? this.records.filter(x => this.group.all_host_ids.includes(x.id)) : [];
|
if (this.group.key) {
|
||||||
if (this.f_word) records = records.filter(x => includes(x.name, this.f_word) || includes(x.public_ip_address, this.f_word) || includes(x.private_ip_address, this.f_word));
|
const host_ids = this.counter[this.group.key]
|
||||||
|
records = this.records.filter(x => host_ids && host_ids.has(x.id));
|
||||||
|
}
|
||||||
if (this.f_status !== '') records = records.filter(x => this.f_status === x.is_verified);
|
if (this.f_status !== '') records = records.filter(x => this.f_status === x.is_verified);
|
||||||
return records
|
return records
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get counter() {
|
||||||
|
const counter = {}
|
||||||
|
for (let host of this.records) {
|
||||||
|
for (let id of host.group_ids) {
|
||||||
|
if (counter[id]) {
|
||||||
|
counter[id].add(host.id)
|
||||||
|
} else {
|
||||||
|
counter[id] = new Set([host.id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let item of this.rawTreeData) {
|
||||||
|
this._handler_counter(item, counter)
|
||||||
|
}
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get treeData() {
|
||||||
|
let treeData = toJS(this.rawTreeData)
|
||||||
|
if (this.f_word) {
|
||||||
|
treeData = this._handle_filter_group(treeData)
|
||||||
|
}
|
||||||
|
return treeData
|
||||||
|
}
|
||||||
|
|
||||||
fetchRecords = () => {
|
fetchRecords = () => {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
return http.get('/api/host/')
|
return http.get('/api/host/')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
const tmp = {};
|
const tmp = {};
|
||||||
this.records = res;
|
this.rawRecords = res;
|
||||||
this.records.map(item => tmp[item.id] = item);
|
this.rawRecords.map(item => tmp[item.id] = item);
|
||||||
this.idMap = tmp;
|
this.idMap = tmp;
|
||||||
this._makeCounter();
|
|
||||||
this.refreshCounter()
|
|
||||||
})
|
})
|
||||||
.finally(() => this.isFetching = false)
|
.finally(() => this.isFetching = false)
|
||||||
};
|
};
|
||||||
|
@ -61,22 +96,22 @@ class Store {
|
||||||
return http.get('/api/host/group/')
|
return http.get('/api/host/group/')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.groups = res.groups;
|
this.groups = res.groups;
|
||||||
this.refreshCounter(res.treeData)
|
this.rawTreeData = res.treeData
|
||||||
})
|
})
|
||||||
.finally(() => this.grpFetching = false)
|
.finally(() => this.grpFetching = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
initial = () => {
|
initial = () => {
|
||||||
|
if (this.rawRecords.length > 0) return Promise.resolve()
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
this.grpFetching = true;
|
this.grpFetching = true;
|
||||||
return http.all([http.get('/api/host/'), http.get('/api/host/group/')])
|
return http.all([http.get('/api/host/'), http.get('/api/host/group/')])
|
||||||
.then(http.spread((res1, res2) => {
|
.then(http.spread((res1, res2) => {
|
||||||
this.records = res1;
|
this.rawRecords = res1;
|
||||||
this.records.map(item => this.idMap[item.id] = item);
|
this.rawRecords.map(item => this.idMap[item.id] = item);
|
||||||
this.group = res2.treeData[0] || {};
|
|
||||||
this.groups = res2.groups;
|
this.groups = res2.groups;
|
||||||
this._makeCounter();
|
this.rawTreeData = res2.treeData;
|
||||||
this.refreshCounter(res2.treeData)
|
this.group = this.treeData[0];
|
||||||
}))
|
}))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
|
@ -112,39 +147,24 @@ class Store {
|
||||||
this.selectorVisible = true;
|
this.selectorVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshCounter = (treeData) => {
|
_handler_counter = (item, counter) => {
|
||||||
treeData = treeData || lds.cloneDeep(this.treeData);
|
if (!counter[item.key]) counter[item.key] = new Set()
|
||||||
if (treeData.length && this.records !== null) {
|
|
||||||
for (let item of treeData) {
|
|
||||||
this._refreshCounter(item)
|
|
||||||
}
|
|
||||||
this.treeData = treeData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_refreshCounter = (item) => {
|
|
||||||
item.all_host_ids = item.self_host_ids = this.counter[item.key] || [];
|
|
||||||
for (let child of item.children) {
|
for (let child of item.children) {
|
||||||
const ids = this._refreshCounter(child)
|
this._handler_counter(child, counter)
|
||||||
item.all_host_ids = item.all_host_ids.concat(ids)
|
counter[child.key].forEach(x => counter[item.key].add(x))
|
||||||
}
|
}
|
||||||
item.all_host_ids = Array.from(new Set(item.all_host_ids));
|
|
||||||
if (this.group.key === item.key) this.group = item;
|
|
||||||
return item.all_host_ids
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_makeCounter = () => {
|
_handle_filter_group = (treeData) => {
|
||||||
const counter = {};
|
const data = []
|
||||||
for (let host of this.records) {
|
for (let item of treeData) {
|
||||||
for (let id of host.group_ids) {
|
const host_ids = this.counter[item.key]
|
||||||
if (counter[id]) {
|
if (host_ids.size > 0 || item.key === this.group.key) {
|
||||||
counter[id].push(host.id)
|
item.children = this._handle_filter_group(item.children)
|
||||||
} else {
|
data.push(item)
|
||||||
counter[id] = [host.id]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.counter = counter
|
return data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
|
||||||
|
* Copyright (c) <spug.dev@gmail.com>
|
||||||
|
* Released under the AGPL-3.0 License.
|
||||||
|
*/
|
||||||
|
import { observable, computed, toJS } from 'mobx';
|
||||||
|
import { includes } from 'libs';
|
||||||
|
|
||||||
|
class Store {
|
||||||
|
@observable rawTreeData = [];
|
||||||
|
@observable rawRecords = [];
|
||||||
|
@observable group = {};
|
||||||
|
@observable onlySelf = false;
|
||||||
|
|
||||||
|
@observable f_word;
|
||||||
|
|
||||||
|
@computed get records() {
|
||||||
|
let records = this.rawRecords;
|
||||||
|
if (this.f_word) {
|
||||||
|
records = records.filter(x => {
|
||||||
|
if (includes(x.name, this.f_word)) return true
|
||||||
|
if (x.public_ip_address && includes(x.public_ip_address[0], this.f_word)) return true
|
||||||
|
return !!(x.private_ip_address && includes(x.private_ip_address[0], this.f_word));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get dataSource() {
|
||||||
|
let records = [];
|
||||||
|
if (this.group.key) {
|
||||||
|
const host_ids = this.counter[this.group.key]
|
||||||
|
records = this.records.filter(x => host_ids && host_ids.has(x.id));
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get counter() {
|
||||||
|
const counter = {}
|
||||||
|
for (let host of this.records) {
|
||||||
|
for (let id of host.group_ids) {
|
||||||
|
if (counter[id]) {
|
||||||
|
counter[id].add(host.id)
|
||||||
|
} else {
|
||||||
|
counter[id] = new Set([host.id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.onlySelf) {
|
||||||
|
for (let item of this.rawTreeData) {
|
||||||
|
this._handler_counter(item, counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get treeData() {
|
||||||
|
let treeData = toJS(this.rawTreeData)
|
||||||
|
if (this.f_word) {
|
||||||
|
treeData = this._handle_filter_group(treeData)
|
||||||
|
}
|
||||||
|
return treeData
|
||||||
|
}
|
||||||
|
|
||||||
|
_handler_counter = (item, counter) => {
|
||||||
|
if (!counter[item.key]) counter[item.key] = new Set()
|
||||||
|
for (let child of item.children) {
|
||||||
|
this._handler_counter(child, counter)
|
||||||
|
counter[child.key].forEach(x => counter[item.key].add(x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handle_filter_group = (treeData) => {
|
||||||
|
const data = []
|
||||||
|
for (let item of treeData) {
|
||||||
|
const host_ids = this.counter[item.key]
|
||||||
|
if (host_ids?.size > 0 || item.key === this.group.key) {
|
||||||
|
item.children = this._handle_filter_group(item.children)
|
||||||
|
data.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Store()
|
|
@ -29,9 +29,10 @@ export default function () {
|
||||||
appStore.records = [];
|
appStore.records = [];
|
||||||
requestStore.records = [];
|
requestStore.records = [];
|
||||||
requestStore.deploys = [];
|
requestStore.deploys = [];
|
||||||
hostStore.records = null;
|
hostStore.rawRecords = [];
|
||||||
|
hostStore.rawTreeData = [];
|
||||||
hostStore.groups = {};
|
hostStore.groups = {};
|
||||||
hostStore.treeData = [];
|
hostStore.group = {};
|
||||||
execStore.hosts = [];
|
execStore.hosts = [];
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ import hostStore from '../host/store';
|
||||||
|
|
||||||
export default observer(function () {
|
export default observer(function () {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
hostStore.initial()
|
||||||
store.targets = store.record.id ? store.record['targets'] : [undefined];
|
store.targets = store.record.id ? store.record['targets'] : [undefined];
|
||||||
if (!hostStore.records || hostStore.records.length === 0) hostStore.fetchRecords()
|
|
||||||
}, [])
|
}, [])
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|
|
@ -30,7 +30,7 @@ export default observer(function () {
|
||||||
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
|
||||||
onChange={v => store.editTarget(index, v)}>
|
onChange={v => store.editTarget(index, v)}>
|
||||||
<Select.Option value="local" disabled={store.targets.includes('local')}>本机</Select.Option>
|
<Select.Option value="local" disabled={store.targets.includes('local')}>本机</Select.Option>
|
||||||
{hostStore.records.map(item => (
|
{hostStore.rawRecords.map(item => (
|
||||||
<Select.Option key={item.id} value={item.id} disabled={store.targets.includes(item.id)}>
|
<Select.Option key={item.id} value={item.id} disabled={store.targets.includes(item.id)}>
|
||||||
{`${item.name}(${item['hostname']}:${item['port']})`}
|
{`${item.name}(${item['hostname']}:${item['port']})`}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
|
|
|
@ -17,9 +17,7 @@ export default observer(function () {
|
||||||
const [groups, setGroups] = useState([...store.record.group_perms]);
|
const [groups, setGroups] = useState([...store.record.group_perms]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hostStore.treeData.length === 0) {
|
hostStore.initial()
|
||||||
hostStore.initial()
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
|
@ -63,7 +61,7 @@ export default observer(function () {
|
||||||
value={id}
|
value={id}
|
||||||
showSearch={false}
|
showSearch={false}
|
||||||
treeNodeLabelProp="name"
|
treeNodeLabelProp="name"
|
||||||
treeData={hostStore.treeData}
|
treeData={hostStore.rawTreeData}
|
||||||
onChange={value => handleChange(index, value)}
|
onChange={value => handleChange(index, value)}
|
||||||
placeholder="请选择分组"/>
|
placeholder="请选择分组"/>
|
||||||
{groups.length > 1 && (
|
{groups.length > 1 && (
|
||||||
|
|
Loading…
Reference in New Issue