From 7872d8cb854e523c196ef5bdcf2c4c66307c693c Mon Sep 17 00:00:00 2001 From: vapao Date: Thu, 7 Jul 2022 09:03:31 +0800 Subject: [PATCH] =?UTF-8?q?U=20=E4=BC=98=E5=8C=96=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E5=88=86=E7=BB=84=E6=94=AF=E6=8C=81=E5=85=A8=E5=B1=80=E6=90=9C?= =?UTF-8?q?=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spug_web/package.json | 6 +- spug_web/src/libs/functools.js | 26 +++- spug_web/src/pages/deploy/request/Ext1Form.js | 2 - spug_web/src/pages/deploy/request/Ext2Form.js | 2 - .../src/pages/deploy/request/HostSelector.js | 6 +- spug_web/src/pages/deploy/request/Rollback.js | 2 - spug_web/src/pages/host/Group.js | 65 ++++++--- spug_web/src/pages/host/Selector.js | 125 +++++++++++------- spug_web/src/pages/host/Table.js | 5 +- spug_web/src/pages/host/index.js | 6 +- spug_web/src/pages/host/index.module.less | 26 +++- spug_web/src/pages/host/store.js | 108 +++++++++------ spug_web/src/pages/host/store2.js | 86 ++++++++++++ spug_web/src/pages/login/index.js | 5 +- spug_web/src/pages/schedule/Form.js | 2 +- spug_web/src/pages/schedule/Step2.js | 2 +- spug_web/src/pages/system/role/HostPerm.js | 6 +- 17 files changed, 336 insertions(+), 144 deletions(-) create mode 100644 spug_web/src/pages/host/store2.js 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} />
- setFKey(e.target.value)}/> + store.f_word = e.target.value}/>
{ @@ -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 && (