diff --git a/spug_web/package.json b/spug_web/package.json
index 229e9da..b24107a 100644
--- a/spug_web/package.json
+++ b/spug_web/package.json
@@ -5,7 +5,7 @@
"dependencies": {
"@ant-design/icons": "^4.3.0",
"ace-builds": "^1.4.13",
- "antd": "^4.19.2",
+ "antd": "4.21.4",
"axios": "^0.21.0",
"bizcharts": "^3.5.9",
"history": "^4.10.1",
@@ -45,9 +45,9 @@
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.10.5",
"customize-cra": "^1.0.0",
+ "http-proxy-middleware": "0.19.2",
"less": "^3.12.2",
"less-loader": "^7.1.0",
- "react-app-rewired": "^2.1.6",
- "http-proxy-middleware": "0.19.2"
+ "react-app-rewired": "^2.1.6"
}
}
diff --git a/spug_web/src/libs/functools.js b/spug_web/src/libs/functools.js
index 66c58d1..0426d84 100644
--- a/spug_web/src/libs/functools.js
+++ b/spug_web/src/libs/functools.js
@@ -39,15 +39,29 @@ export function clsNames(...args) {
return args.filter(x => x).join(' ')
}
-export function includes(s, key) {
- key = key.toLowerCase();
- if (Array.isArray(s)) {
- for (let i of s) {
- if (i && i.toLowerCase().includes(key)) return true
+function isInclude(s, keys) {
+ if (!s) return false
+ if (Array.isArray(keys)) {
+ for (let k of keys) {
+ k = k.toLowerCase()
+ if (s.toLowerCase().includes(k)) return true
}
return false
} 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)
}
}
diff --git a/spug_web/src/pages/deploy/request/Ext1Form.js b/spug_web/src/pages/deploy/request/Ext1Form.js
index 52d7a84..4be9330 100644
--- a/spug_web/src/pages/deploy/request/Ext1Form.js
+++ b/spug_web/src/pages/deploy/request/Ext1Form.js
@@ -8,7 +8,6 @@ import { observer } from 'mobx-react';
import { Modal, Form, Input, Select, DatePicker, Button, message } from 'antd';
import { LoadingOutlined, SyncOutlined } from '@ant-design/icons';
import HostSelector from './HostSelector';
-import hostStore from 'pages/host/store';
import { http, history } from 'libs';
import store from './store';
import lds from 'lodash';
@@ -44,7 +43,6 @@ export default observer(function () {
useEffect(() => {
const {app_host_ids, host_ids} = store.record;
setHostIds(lds.clone(host_ids || app_host_ids));
- if (!hostStore.records || hostStore.records.length === 0) hostStore.fetchRecords()
fetchVersions()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
diff --git a/spug_web/src/pages/deploy/request/Ext2Form.js b/spug_web/src/pages/deploy/request/Ext2Form.js
index ff8cdb4..a05c4da 100644
--- a/spug_web/src/pages/deploy/request/Ext2Form.js
+++ b/spug_web/src/pages/deploy/request/Ext2Form.js
@@ -7,7 +7,6 @@ import React, { useState, useEffect } from 'react';
import { observer } from 'mobx-react';
import { UploadOutlined } from '@ant-design/icons';
import { Modal, Form, Input, Upload, DatePicker, message, Button } from 'antd';
-import hostStore from 'pages/host/store';
import HostSelector from './HostSelector';
import { http, clsNames, X_TOKEN } from 'libs';
import styles from './index.module.less';
@@ -26,7 +25,6 @@ export default observer(function () {
useEffect(() => {
const {app_host_ids, host_ids, extra} = store.record;
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'}])
}, [])
diff --git a/spug_web/src/pages/deploy/request/HostSelector.js b/spug_web/src/pages/deploy/request/HostSelector.js
index 7addafb..882d659 100644
--- a/spug_web/src/pages/deploy/request/HostSelector.js
+++ b/spug_web/src/pages/deploy/request/HostSelector.js
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { observer } from 'mobx-react';
import { Modal, Table, Button, Alert } from 'antd';
import hostStore from 'pages/host/store';
@@ -7,6 +7,10 @@ import lds from 'lodash';
export default observer(function (props) {
const [selectedRowKeys, setSelectedRowKeys] = useState(props.host_ids || []);
+ useEffect(() => {
+ hostStore.initial()
+ }, [])
+
function handleClickRow(record) {
const index = selectedRowKeys.indexOf(record.id);
if (index !== -1) {
diff --git a/spug_web/src/pages/deploy/request/Rollback.js b/spug_web/src/pages/deploy/request/Rollback.js
index 795c3d7..b4c59d2 100644
--- a/spug_web/src/pages/deploy/request/Rollback.js
+++ b/spug_web/src/pages/deploy/request/Rollback.js
@@ -7,7 +7,6 @@ import React, { useState, useEffect } from 'react';
import { observer } from 'mobx-react';
import { Modal, Form, Input, Select, Button, message } from 'antd';
import HostSelector from './HostSelector';
-import hostStore from 'pages/host/store';
import { http, includes } from 'libs';
import store from './store';
import lds from 'lodash';
@@ -22,7 +21,6 @@ export default observer(function () {
useEffect(() => {
const {app_host_ids, host_ids} = store.record;
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
}, [])
diff --git a/spug_web/src/pages/host/Group.js b/spug_web/src/pages/host/Group.js
index 6c1ab54..7744159 100644
--- a/spug_web/src/pages/host/Group.js
+++ b/spug_web/src/pages/host/Group.js
@@ -9,6 +9,7 @@ import { Input, Card, Tree, Dropdown, Menu, Switch, Tooltip, Spin, Modal } from
import {
FolderOutlined,
FolderAddOutlined,
+ FolderOpenOutlined,
EditOutlined,
DeleteOutlined,
CopyOutlined,
@@ -24,11 +25,12 @@ import store from './store';
import lds from 'lodash';
export default observer(function () {
+ const [isReady, setIsReady] = useState(false);
const [loading, setLoading] = useState();
const [visible, setVisible] = useState(false);
const [draggable, setDraggable] = useState(false);
const [action, setAction] = useState('');
- const [expands, setExpands] = useState();
+ const [expands, setExpands] = useState([]);
const [bakTreeData, setBakTreeData] = useState();
useEffect(() => {
@@ -36,10 +38,13 @@ export default observer(function () {
}, [loading])
useEffect(() => {
- const length = store.treeData.length
- if (length > 0 && length < 5 && expands === undefined) {
- const tmp = store.treeData.filter(x => x.children.length)
- setExpands(tmp.map(x => x.key))
+ 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])
@@ -50,10 +55,10 @@ export default observer(function () {
} onClick={handleAdd}>新建子分组
} onClick={() => setAction('edit')}>重命名
- } onClick={() => store.showSelector(true)}>添加至分组
- } onClick={() => store.showSelector(false)}>移动至分组
-
+ } onClick={() => store.showSelector(true)}>添加主机
+ } onClick={() => store.showSelector(false)}>移动主机
} danger onClick={handleRemoveHosts}>删除主机
+
} danger onClick={handleRemove}>删除此分组
)
@@ -66,7 +71,7 @@ export default observer(function () {
.then(() => setAction(''))
.finally(() => setLoading(false))
} else {
- if (store.group.key === 0) store.treeData = bakTreeData
+ if (store.group.key === 0) store.rawTreeData = bakTreeData
setAction('')
}
}
@@ -75,9 +80,9 @@ export default observer(function () {
const group = store.group;
Modal.confirm({
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}})
- .then(store.initial)
+ .then(store.fetchRecords)
})
}
@@ -92,24 +97,34 @@ export default observer(function () {
}
function handleAddRoot() {
- setBakTreeData(lds.cloneDeep(store.treeData));
- const current = {key: 0, parent_id: 0, title: ''};
- store.treeData.unshift(current);
- store.treeData = lds.cloneDeep(store.treeData);
+ setBakTreeData(lds.cloneDeep(store.rawTreeData));
+ const current = {key: 0, parent_id: 0, title: '', children: []};
+ store.rawTreeData.unshift(current);
+ store.rawTreeData = lds.cloneDeep(store.rawTreeData);
store.group = current;
setAction('edit')
}
function handleAdd() {
- setBakTreeData(lds.cloneDeep(store.treeData));
- const current = {key: 0, parent_id: store.group.key, title: ''};
- store.group.children.unshift(current);
- store.treeData = lds.cloneDeep(store.treeData);
+ setBakTreeData(lds.cloneDeep(store.rawTreeData));
+ const current = {key: 0, parent_id: store.group.key, title: '', children: []};
+ const node = _find_node(store.rawTreeData, store.group.key)
+ node.children.unshift(current)
+ store.rawTreeData = lds.cloneDeep(store.rawTreeData);
if (!expands.includes(store.group.key)) setExpands([store.group.key, ...expands]);
store.group = current;
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) {
setLoading(true);
const pos = v.node.pos.split('-');
@@ -138,6 +153,7 @@ export default observer(function () {
size="small"
style={{width: 'calc(100% - 24px)'}}
defaultValue={nodeData.title}
+ placeholder="请输入"
suffix={loading ? : }
onClick={e => e.stopPropagation()}
onBlur={handleSubmit}
@@ -146,9 +162,13 @@ export default observer(function () {
} else if (action === 'del' && nodeData.key === store.group.key) {
return
} 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 (
- {nodeData.title}{extend}
+
+ {expands.includes(nodeData.key) ?
:
}
+
{nodeData.title}
+ {length ?
{length}
: null}
+
)
}
}
@@ -165,7 +185,7 @@ export default observer(function () {
onChange={setDraggable}
checkedChildren="排版"
unCheckedChildren="浏览"/>
-
+
)}>
@@ -176,6 +196,7 @@ export default observer(function () {
trigger={['contextMenu']}
onVisibleChange={v => v || setVisible(v)}>
{
- if (!store.treeData.length) {
- store.initial()
- .then(() => setGroup(store.treeData[0] || {}))
- } else {
- setGroup(store.treeData[0] || {})
- }
+ store.onlySelf = props.onlySelf;
+ hStore.initial().then(() => {
+ store.rawRecords = hStore.rawRecords;
+ store.rawTreeData = hStore.rawTreeData;
+ 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(() => {
setSelectedRowKeys(props.selectedRowKeys || [])
}, [props.selectedRowKeys])
useEffect(() => {
- let records = store.records;
- if (group.key) records = records.filter(x => group.self_host_ids.includes(x.id));
- if (fKey) records = records.filter(x => includes(x.name, fKey) || includes(x.hostname, fKey));
- setDataSource(records)
- }, [group, fKey])
-
- function treeRender(nodeData) {
- return (
-
- {nodeData.title}{nodeData.self_host_ids && nodeData.self_host_ids.length ? `(${nodeData.self_host_ids.length})` : null}
-
- )
- }
+ if (props.oneGroup) {
+ setSelectedRowKeys([])
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [store.group])
function handleClickRow(record) {
- let tmp = [...selectedRowKeys]
- const index = tmp.indexOf(record.id);
- if (index !== -1) {
- tmp.splice(index, 1)
- } else if (props.onlyOne) {
- tmp = [record.id]
- } else {
- tmp.push(record.id)
+ let tmp = new Set(selectedRowKeys)
+ if (!tmp.delete(record.id)) {
+ if (props.onlyOne) tmp.clear()
+ tmp.add(record.id)
}
- setSelectedRowKeys(tmp)
+ setSelectedRowKeys([...tmp])
}
function handleSubmit() {
if (props.onOk) {
setLoading(true);
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) {
- res = props.onOk(group, selectedRowKeys[0], selectedRows[0])
+ res = props.onOk(store.group, selectedRowKeys[0], selectedRows[0])
} else {
- res = props.onOk(group, selectedRowKeys, selectedRows);
+ res = props.onOk(store.group, selectedRowKeys, selectedRows);
}
if (res && res.then) {
res.then(props.onCancel, () => setLoading(false))
@@ -77,9 +79,33 @@ export default observer(function (props) {
}
}
- function handleChangeGrp(node) {
- setGroup(node);
- if (props.oneGroup) setSelectedRowKeys([])
+ function handleExpand(keys, {_, node}) {
+ if (node.children.length > 0) {
+ 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 (
+
+ {expands.includes(nodeData.key) ?
:
}
+
{nodeData.title}
+ {length ?
{length}
: null}
+
+ )
}
return (
@@ -95,25 +121,29 @@ export default observer(function (props) {
0 && store.treeData.length < 5}
+ showIcon={false}
+ autoExpandParent
expandAction="doubleClick"
- selectedKeys={[group.key]}
+ selectedKeys={[store.group.key]}
+ expandedKeys={expands}
treeData={store.treeData}
titleRender={treeRender}
- onSelect={(_, {node}) => handleChangeGrp(node)}
+ onExpand={handleExpand}
+ onSelect={(_, {node}) => store.group = node}
/>
{
@@ -123,11 +153,12 @@ export default observer(function (props) {
}}
rowSelection={{
selectedRowKeys,
+ hideSelectAll: props.onlyOne,
onSelect: handleClickRow,
- onSelectAll: (_, __, changeRows) => changeRows.map(x => handleClickRow(x))
+ onSelectAll: handleSelectAll
}}>
a.name.localeCompare(b.name)}/>
- a.name.localeCompare(b.name)}/>
+ a.name.localeCompare(b.name)}/>
diff --git a/spug_web/src/pages/host/Table.js b/spug_web/src/pages/host/Table.js
index 9f434d0..355011d 100644
--- a/spug_web/src/pages/host/Table.js
+++ b/spug_web/src/pages/host/Table.js
@@ -40,7 +40,7 @@ function ComTable() {
function IpAddress(props) {
if (props.ip && props.ip.length > 0) {
return (
- {props.ip[0]}({props.isPublic ? '公' : '私有'})
+ {props.ip[0]}({props.isPublic ? '公' : '私'})
)
} else {
return null
@@ -51,7 +51,8 @@ function ComTable() {
store.f_word = e.target.value}/>}
+ title={ store.f_word = e.target.value}/>}
loading={store.isFetching}
dataSource={store.dataSource}
onReload={store.fetchRecords}
diff --git a/spug_web/src/pages/host/index.js b/spug_web/src/pages/host/index.js
index 44cd52d..fc43907 100644
--- a/spug_web/src/pages/host/index.js
+++ b/spug_web/src/pages/host/index.js
@@ -50,7 +50,11 @@ export default observer(function () {
{store.cloudImport && }
{store.syncVisible && }
{store.selectorVisible &&
- store.selectorVisible = false} onOk={store.updateGroup}/>}
+ store.selectorVisible = false}
+ onOk={store.updateGroup}
+ />}
);
})
diff --git a/spug_web/src/pages/host/index.module.less b/spug_web/src/pages/host/index.module.less
index 27c2ccd..9e900ba 100644
--- a/spug_web/src/pages/host/index.module.less
+++ b/spug_web/src/pages/host/index.module.less
@@ -26,8 +26,10 @@
}
}
-.selector :global(.ant-modal-footer) {
- border-top: none
+.selector {
+ :global(.ant-modal-footer) {
+ border-top: none
+ }
}
.formAddress1 {
@@ -62,11 +64,27 @@
.group {
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;
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;
}
}
diff --git a/spug_web/src/pages/host/store.js b/spug_web/src/pages/host/store.js
index 1416242..6a11967 100644
--- a/spug_web/src/pages/host/store.js
+++ b/spug_web/src/pages/host/store.js
@@ -3,15 +3,13 @@
* Copyright (c)
* Released under the AGPL-3.0 License.
*/
-import { observable, computed } from 'mobx';
+import { observable, computed, toJS } from 'mobx';
import { message } from 'antd';
import { http, includes } from 'libs';
-import lds from 'lodash';
class Store {
- counter = {};
- @observable records = null;
- @observable treeData = [];
+ @observable rawTreeData = [];
+ @observable rawRecords = [];
@observable groups = {};
@observable group = {};
@observable record = {};
@@ -29,24 +27,61 @@ class Store {
@observable f_word;
@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() {
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.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));
+ if (this.group.key) {
+ 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);
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 = () => {
this.isFetching = true;
return http.get('/api/host/')
.then(res => {
const tmp = {};
- this.records = res;
- this.records.map(item => tmp[item.id] = item);
+ this.rawRecords = res;
+ this.rawRecords.map(item => tmp[item.id] = item);
this.idMap = tmp;
- this._makeCounter();
- this.refreshCounter()
})
.finally(() => this.isFetching = false)
};
@@ -61,22 +96,22 @@ class Store {
return http.get('/api/host/group/')
.then(res => {
this.groups = res.groups;
- this.refreshCounter(res.treeData)
+ this.rawTreeData = res.treeData
})
.finally(() => this.grpFetching = false)
}
initial = () => {
+ if (this.rawRecords.length > 0) return Promise.resolve()
this.isFetching = true;
this.grpFetching = true;
return http.all([http.get('/api/host/'), http.get('/api/host/group/')])
.then(http.spread((res1, res2) => {
- this.records = res1;
- this.records.map(item => this.idMap[item.id] = item);
- this.group = res2.treeData[0] || {};
+ this.rawRecords = res1;
+ this.rawRecords.map(item => this.idMap[item.id] = item);
this.groups = res2.groups;
- this._makeCounter();
- this.refreshCounter(res2.treeData)
+ this.rawTreeData = res2.treeData;
+ this.group = this.treeData[0];
}))
.finally(() => {
this.isFetching = false;
@@ -112,39 +147,24 @@ class Store {
this.selectorVisible = true;
}
- refreshCounter = (treeData) => {
- treeData = treeData || lds.cloneDeep(this.treeData);
- 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] || [];
+ _handler_counter = (item, counter) => {
+ if (!counter[item.key]) counter[item.key] = new Set()
for (let child of item.children) {
- const ids = this._refreshCounter(child)
- item.all_host_ids = item.all_host_ids.concat(ids)
+ this._handler_counter(child, counter)
+ 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 = () => {
- const counter = {};
- for (let host of this.records) {
- for (let id of host.group_ids) {
- if (counter[id]) {
- counter[id].push(host.id)
- } else {
- counter[id] = [host.id]
- }
+ _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)
}
}
- this.counter = counter
+ return data
}
}
diff --git a/spug_web/src/pages/host/store2.js b/spug_web/src/pages/host/store2.js
new file mode 100644
index 0000000..f14fe3d
--- /dev/null
+++ b/spug_web/src/pages/host/store2.js
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug
+ * Copyright (c)
+ * 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()
diff --git a/spug_web/src/pages/login/index.js b/spug_web/src/pages/login/index.js
index 5d9a093..8fe07f4 100644
--- a/spug_web/src/pages/login/index.js
+++ b/spug_web/src/pages/login/index.js
@@ -29,9 +29,10 @@ export default function () {
appStore.records = [];
requestStore.records = [];
requestStore.deploys = [];
- hostStore.records = null;
+ hostStore.rawRecords = [];
+ hostStore.rawTreeData = [];
hostStore.groups = {};
- hostStore.treeData = [];
+ hostStore.group = {};
execStore.hosts = [];
}, [])
diff --git a/spug_web/src/pages/schedule/Form.js b/spug_web/src/pages/schedule/Form.js
index d554978..de662cf 100644
--- a/spug_web/src/pages/schedule/Form.js
+++ b/spug_web/src/pages/schedule/Form.js
@@ -15,8 +15,8 @@ import hostStore from '../host/store';
export default observer(function () {
useEffect(() => {
+ hostStore.initial()
store.targets = store.record.id ? store.record['targets'] : [undefined];
- if (!hostStore.records || hostStore.records.length === 0) hostStore.fetchRecords()
}, [])
return (
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
onChange={v => store.editTarget(index, v)}>
本机
- {hostStore.records.map(item => (
+ {hostStore.rawRecords.map(item => (
{`${item.name}(${item['hostname']}:${item['port']})`}
diff --git a/spug_web/src/pages/system/role/HostPerm.js b/spug_web/src/pages/system/role/HostPerm.js
index cc29605..4a2e93d 100644
--- a/spug_web/src/pages/system/role/HostPerm.js
+++ b/spug_web/src/pages/system/role/HostPerm.js
@@ -17,9 +17,7 @@ export default observer(function () {
const [groups, setGroups] = useState([...store.record.group_perms]);
useEffect(() => {
- if (hostStore.treeData.length === 0) {
- hostStore.initial()
- }
+ hostStore.initial()
}, [])
function handleSubmit() {
@@ -63,7 +61,7 @@ export default observer(function () {
value={id}
showSearch={false}
treeNodeLabelProp="name"
- treeData={hostStore.treeData}
+ treeData={hostStore.rawTreeData}
onChange={value => handleChange(index, value)}
placeholder="请选择分组"/>
{groups.length > 1 && (