diff --git a/spug_api/apps/host/group.py b/spug_api/apps/host/group.py
index f11f894..de23da6 100644
--- a/spug_api/apps/host/group.py
+++ b/spug_api/apps/host/group.py
@@ -18,8 +18,9 @@ def fetch_children(data):
def merge_children(data, prefix, childes):
+ prefix = f'{prefix}/' if prefix else ''
for item in childes:
- name = f'{prefix}/{item["title"]}'
+ name = f'{prefix}{item["title"]}'
item['name'] = name
if item['children']:
merge_children(data, name, item['children'])
diff --git a/spug_api/apps/host/models.py b/spug_api/apps/host/models.py
index e235a55..3d48fa9 100644
--- a/spug_api/apps/host/models.py
+++ b/spug_api/apps/host/models.py
@@ -46,7 +46,7 @@ class Group(models.Model, ModelMixin):
name = models.CharField(max_length=20)
parent_id = models.IntegerField(default=0)
sort_id = models.IntegerField(default=0)
- hosts = models.ManyToManyField(Host, through='HostGroupRel', related_name='groups')
+ hosts = models.ManyToManyField(Host, related_name='groups')
def to_view(self):
return {
@@ -59,11 +59,3 @@ class Group(models.Model, ModelMixin):
class Meta:
db_table = 'host_groups'
ordering = ('-sort_id',)
-
-
-class HostGroupRel(models.Model):
- group = models.ForeignKey(Group, on_delete=models.CASCADE)
- host = models.ForeignKey(Host, on_delete=models.CASCADE)
-
- class Meta:
- db_table = 'host_group_rel'
diff --git a/spug_api/apps/host/views.py b/spug_api/apps/host/views.py
index 07de7c8..cc454ec 100644
--- a/spug_api/apps/host/views.py
+++ b/spug_api/apps/host/views.py
@@ -6,7 +6,7 @@ from django.db.models import F
from django.http.response import HttpResponseBadRequest
from libs import json_response, JsonParser, Argument
from apps.setting.utils import AppSetting
-from apps.host.models import Host, HostGroupRel
+from apps.host.models import Host, Group
from apps.app.models import Deploy
from apps.schedule.models import Task
from apps.monitor.models import Detection
@@ -26,7 +26,7 @@ class HostView(View):
return json_response(error='无权访问该主机,请联系管理员')
return json_response(Host.objects.get(pk=host_id))
hosts = {x.id: x.to_view() for x in Host.objects.filter(deleted_by_id__isnull=True)}
- for rel in HostGroupRel.objects.all():
+ for rel in Group.hosts.through.objects.all():
hosts[rel.host_id]['group_ids'].append(rel.group_id)
return json_response(list(hosts.values()))
@@ -61,15 +61,17 @@ class HostView(View):
def patch(self, request):
form, error = JsonParser(
- Argument('id', type=int, required=False),
- Argument('zone', help='请输入主机类别')
+ Argument('host_ids', type=list, filter=lambda x: len(x), help='请选择主机'),
+ Argument('s_group_id', type=int, help='参数错误'),
+ Argument('t_group_id', type=int, help='参数错误'),
+ Argument('is_copy', type=bool, help='参数错误'),
).parse(request.body)
if error is None:
- host = Host.objects.filter(pk=form.id).first()
- if not host:
- return json_response(error='未找到指定主机')
- count = Host.objects.filter(zone=host.zone, deleted_by_id__isnull=True).update(zone=form.zone)
- return json_response(count)
+ s_group = Group.objects.get(pk=form.s_group_id)
+ t_group = Group.objects.get(pk=form.t_group_id)
+ t_group.hosts.add(*form.host_ids)
+ if not form.is_copy:
+ s_group.hosts.remove(*form.host_ids)
return json_response(error=error)
def delete(self, request):
diff --git a/spug_web/src/pages/host/Detail.js b/spug_web/src/pages/host/Detail.js
index f50e728..5b8fc61 100644
--- a/spug_web/src/pages/host/Detail.js
+++ b/spug_web/src/pages/host/Detail.js
@@ -1,17 +1,31 @@
import React from 'react';
import { observer } from 'mobx-react';
-import { Drawer} from 'antd';
+import { Drawer, Descriptions, List } from 'antd';
import store from './store';
export default observer(function () {
+ const host = store.record;
return (
store.detailVisible = false}
visible={store.detailVisible}>
-
+ 基本信息} column={1}>
+ {host.name}
+ {host.username}@{host.hostname}
+ {host.port}
+ {host.pkey ? '是' : '否'}
+ {host.desc}
+
+
+ 腾讯云/华北区
+ 腾讯云/测试环境/电商商城系统
+ 腾讯云/测试环境/订单后台系统
+
+
+
)
})
\ No newline at end of file
diff --git a/spug_web/src/pages/host/Group.js b/spug_web/src/pages/host/Group.js
index 743240d..ccf6a48 100644
--- a/spug_web/src/pages/host/Group.js
+++ b/spug_web/src/pages/host/Group.js
@@ -6,6 +6,14 @@
import React, { useState, useEffect } from 'react';
import { observer } from 'mobx-react';
import { Input, Card, Tree, Dropdown, Menu, Switch, message } from 'antd';
+import {
+ FolderOutlined,
+ FolderAddOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ CopyOutlined,
+ ScissorOutlined
+} from '@ant-design/icons';
import { LoadingOutlined } from '@ant-design/icons';
import styles from './index.module.css';
import { http } from 'libs';
@@ -27,10 +35,14 @@ export default observer(function () {
const menus = (
)
@@ -132,7 +144,7 @@ export default observer(function () {
className={styles.dragBox}
autoExpandParent
draggable={draggable}
- treeData={store.treeData}
+ treeData={store.allTreeData}
titleRender={treeRender}
expandedKeys={expands}
selectedKeys={[store.group.key]}
diff --git a/spug_web/src/pages/host/Selector.js b/spug_web/src/pages/host/Selector.js
new file mode 100644
index 0000000..9882a7d
--- /dev/null
+++ b/spug_web/src/pages/host/Selector.js
@@ -0,0 +1,95 @@
+import React, { useEffect, useState } from 'react';
+import { observer } from 'mobx-react';
+import { Modal, Row, Col, Tree, Table } from 'antd';
+import styles from './index.module.css';
+import store from './store';
+
+export default observer(function (props) {
+ const [loading, setLoading] = useState(false);
+ const [group, setGroup] = useState({});
+ const [dataSource, setDataSource] = useState([]);
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+
+ useEffect(() => {
+ if (!store.selfTreeData.length) {
+ store.fetchRecords()
+ store.fetchGroups()
+ .then(() => setGroup(store.selfTreeData[0]))
+ } else {
+ setGroup(store.selfTreeData[0])
+ }
+ }, [])
+
+ useEffect(() => {
+ if (group.key) {
+ const records = store.records.filter(x => group.host_ids.includes(x.id));
+ setDataSource(records)
+ }
+ }, [group])
+
+ function treeRender(nodeData) {
+ return (
+
+ {nodeData.title}{nodeData.host_ids && nodeData.host_ids.length ? `(${nodeData.host_ids.length})` : null}
+
+ )
+ }
+
+ function handleClickRow(record) {
+ const index = selectedRowKeys.indexOf(record.id);
+ if (index !== -1) {
+ selectedRowKeys.splice(index, 1)
+ } else {
+ selectedRowKeys.push(record.id)
+ }
+ setSelectedRowKeys([...selectedRowKeys])
+ }
+
+ function handleSubmit() {
+ if (props.onOk) {
+ setLoading(true)
+ props.onOk(group, selectedRowKeys)
+ .then(props.onCancel, () => setLoading(false))
+ }
+ }
+
+ return (
+
+
+
+ setGroup(node)}
+ />
+
+
+ {
+ return {
+ onClick: () => handleClickRow(record)
+ }
+ }}
+ rowSelection={{
+ selectedRowKeys,
+ onChange: (keys) => setSelectedRowKeys(keys)
+ }}>
+ a.name.localeCompare(b.name)}/>
+ a.name.localeCompare(b.name)}/>
+
+
+
+
+
+ )
+})
\ No newline at end of file
diff --git a/spug_web/src/pages/host/index.js b/spug_web/src/pages/host/index.js
index a419faa..a68d155 100644
--- a/spug_web/src/pages/host/index.js
+++ b/spug_web/src/pages/host/index.js
@@ -12,6 +12,7 @@ import ComTable from './Table';
import ComForm from './Form';
import ComImport from './Import';
import Detail from './Detail';
+import Selector from './Selector';
import store from './store';
export default observer(function () {
@@ -34,6 +35,7 @@ export default observer(function () {
{store.formVisible && }
{store.importVisible && }
+ {store.selectorVisible && store.selectorVisible = false} onOk={store.updateGroup}/>}
);
})
diff --git a/spug_web/src/pages/host/store.js b/spug_web/src/pages/host/store.js
index 8365888..281ee8d 100644
--- a/spug_web/src/pages/host/store.js
+++ b/spug_web/src/pages/host/store.js
@@ -4,7 +4,9 @@
* Released under the AGPL-3.0 License.
*/
import { observable, computed } from 'mobx';
+import { message } from 'antd';
import http from 'libs/http';
+import lds from 'lodash';
class Store {
@observable records = [];
@@ -13,15 +15,47 @@ class Store {
@observable group = {};
@observable record = {};
@observable idMap = {};
+ @observable addByCopy = true;
@observable grpFetching = true;
@observable isFetching = false;
@observable formVisible = false;
@observable importVisible = false;
@observable detailVisible = false;
+ @observable selectorVisible = false;
@observable f_name;
@observable f_host;
+ @computed get counter() {
+ 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]
+ }
+ }
+ }
+ return counter
+ }
+
+ @computed get selfTreeData() {
+ const treeData = lds.cloneDeep(this.treeData);
+ for (let item of treeData) {
+ this._updateCounter(item, true)
+ }
+ return treeData
+ }
+
+ @computed get allTreeData() {
+ const treeData = lds.cloneDeep(this.treeData);
+ for (let item of treeData) {
+ this._updateCounter(item, false)
+ }
+ return treeData
+ }
+
@computed get dataSource() {
let records = [];
if (this.group.host_ids) records = this.records.filter(x => this.group.host_ids.includes(x.id));
@@ -38,23 +72,30 @@ class Store {
for (let item of this.records) {
this.idMap[item.id] = item
}
- this._updateGroupCount()
})
.finally(() => this.isFetching = false)
};
fetchGroups = () => {
this.grpFetching = true;
- http.get('/api/host/group/')
+ return http.get('/api/host/group/')
.then(res => {
this.treeData = res.treeData;
this.groups = res.groups;
if (!this.group.key) this.group = this.treeData[0];
- this._updateGroupCount()
})
.finally(() => this.grpFetching = false)
}
+ updateGroup = (group, host_ids) => {
+ const form = {host_ids, s_group_id: group.key, t_group_id: this.group.key, is_copy: this.addByCopy};
+ return http.patch('/api/host/', form)
+ .then(() => {
+ message.success('操作成功');
+ this.fetchRecords()
+ })
+ }
+
showForm = (info = {}) => {
this.formVisible = true;
this.record = info
@@ -65,31 +106,18 @@ class Store {
this.detailVisible = true;
}
- _updateGroupCount = () => {
- if (this.treeData.length && this.records.length) {
- 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]
- }
- }
- }
- for (let item of this.treeData) {
- this._updateCount(counter, item)
- }
- }
+ showSelector = (addByCopy) => {
+ this.addByCopy = addByCopy;
+ this.selectorVisible = true;
}
- _updateCount = (counter, item) => {
- let host_ids = counter[item.key] || [];
+ _updateCounter = (item, isSelf) => {
+ let host_ids = this.counter[item.key] || [];
for (let child of item.children) {
- host_ids = host_ids.concat(this._updateCount(counter, child))
+ const ids = this._updateCounter(child, isSelf)
+ if (!isSelf) host_ids = host_ids.concat(ids)
}
item.host_ids = Array.from(new Set(host_ids));
- if (item.key === this.group.key) this.group = item;
return item.host_ids
}
}