简介

CMDB的全称是Configuration Management Database配置管理数据库,它是支撑自动化平台的核心基础模块。本文主要分享下如何使用LayUI开发一个树形结构的资产管理节点。


编写Django接口

LayUI的tree模块数据源属性

data1 = [{
title: '主节点1'
,id: 1
,children: [{
title: '子节点'
,id: 1000]
}]
},{
title: '主节点2'
,id: 2
,children: [{
title: '子节点1'
,id: 2000
},{
title: '子节点2'
,id: 2001
}]
}]


定义host_type_tree_api视图,请求方式为GET,获取的是树形节点数据;

请求方式为POST,重命名主机类型节点和主机。


# 获取主机类型,需要按照layui tree模块的data数据源格式定义参数
@transaction.atomic
def host_type_tree_api(request):
if request.method == "GET":
date = []
#Asset_host_type是资产数(主机类型)
for type in Asset_host_type.objects.all():
children = []
host_type = type.host_type
host_type_id = type.id
host_name = "None"
if host_type == "all_host":
pass
else:
#查询资产树下面的节点(节点)
for h in type.asset_host_set.filter(host_type_id=host_type_id):
host_name_id = h.id
host_name = {"title": h.host_name,"host_name_id": host_name_id}
# children列表里面是主机
children.append(host_name)
#节点标题属性名称要为title,children子节点下面也是如此
asset_host = {"title": host_type,"id":host_type_id, "children": children}
date.append(asset_host)
code = 0
msg = "获取主机类别成功"
res = {"code": code, "msg": msg, "data": date}
return JsonResponse(res)
#重命名节点
elif request.method == "POST":
host_type = request.POST.get("host_type")
host_type_id = request.POST.get("host_type_id")
children_host_name = request.POST.get("children_host_name","None")
host_name = request.POST.get("host_name")
host_name_id = request.POST.get("host_name_id")
try:
#children_host_name为None,表示需要重命名主机节点
if children_host_name == "None":
host = Asset_host.objects.get(id=host_name_id)
host.host_name = host_name
host.save()
code = 0
msg = "修改成功"
else:
host_type_update = Asset_host_type.objects.get(id=host_type_id)
host_type_update.host_type = host_type
host_type_update.save()
code = 0
msg = "修改成功"
except Exception as e:
code = 1
msg = "修改失败"
res = {"code":code,"msg":msg}
return JsonResponse(res)


返回JSON数据


{
'code': 0,
'msg': '获取主机类别成功',
'data': [{
'title': 'pre-k8s-service',
'id': 15,
'children': []
}, {
'title': 'pro-data-service',
'id': 17,
'children': []
}, {
'title': 'pro-k8s-service',
'id': 10,
'children': [{
'title': 'pro-k8s-86',
'host_name_id': 6
}]
}, {
'title': 'pro-yunwei-service',
'id': 16,
'children': []
}, {
'title': 'test-data-service',
'id': 18,
'children': []
}, {
'title': 'test-k8s-service',
'id': 1,
'children': [{
'title': 'test-k8s-216',
'host_name_id': 5
}, {
'title': 'test-k8s-217',
'host_name_id': 8
}]
}, {
'title': 'test-yunwei-service',
'id': 4,
'children': [{
'title': 'test-jenkins-213',
'host_name_id': 4
}]
}]
}


定义host_type_delete视图,删除资产树和主机



def host_type_delete(request):
if request.method == "DELETE":
request_data = QueryDict(request.body)
host_type = request_data.get("host_type")
host_name = request_data.get("host_name")
children_host_name = request_data.get("children_host_name","None")
try:
if children_host_name == "None": #为None 删除的是主机
deleteResult = Asset_host.objects.filter(host_name=host_name).delete()
if deleteResult:
code = 0
msg = "删除成功"
else:
code = 1
msg = "删除失败"
else:
if host_type == "all_host":
code = 1
msg = "系统主机类,无法删除."
else:
# 查询要删除的主机类型
host_type_del = Asset_host_type.objects.get(host_type=host_type)
# 反向查询出该类型下面有多少主机
host = host_type_del.asset_host_set.all().count()
if host > 0:
code = 2
msg = f"{host_type}主机类型存有{host}台主机,请移除,再做删除!"
else:
deleteResult = Asset_host_type.objects.filter(host_type=host_type).delete()
if deleteResult:
code = 0
msg = "删除成功"
else:
code = 1
msg = "删除失败"
except Exception as e:
code = 1
msg = "删除失败"
res = {"code": code, "msg": msg}
return JsonResponse(res)


定义host_type_tree_detail_api视图,当点击资产树(主机类型)的时候,列出属于该资产树的主机,点击主机节点的时候,列出主机的详情,表格形式展示。




#点击左侧主机类型,列出的详情页
def host_type_tree_detail_api(request):
if request.method == "GET":
host_type = request.GET.get("host_type")
host_type_id = request.GET.get("host_type_id")
host_name = request.GET.get("host_name","None")
host_name_id = request.GET.get("host_name_id","None")
date = []
if host_name == "None":
type = Asset_host_type.objects.get(host_type=host_type)
for h in type.asset_host_set.filter(host_type_id=host_type_id):


host_name = h.host_name
remarks_info = h.remarks_info
host_name = {"host_type":host_type,"host_name": host_name, "remarks_info": remarks_info}
date.append(host_name)
else:
host = Asset_host.objects.filter(host_name=host_name).first()
host_name = host.host_name
host_ip = host.host_ip
host_port = host.host_port
remarks_info = host.remarks_info
# 正向查询
host_type = host.host_type.host_type
computer_room_name = host.computer_room.computer_room_name
host_info = {"host_name": host_name, "host_ip": host_ip, "host_port": host_port,
"host_type": host_type, "computer_room_name": computer_room_name,
"remarks_info": remarks_info}
date.append(host_info)
code = 0
msg = "获取主机类别成功"
res = {"code": code, "msg": msg, "data": date}
return JsonResponse(res)


编写前端页面展示树形节点

HTML


<div class="layui-card">
<div class="layui-card-body">
<div class="layui-row">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-body">
<button data-method="notice" class="layui-btn" style="float: left;" id="createHostype">创建</button>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-body">
<div id="treeType" class="demo-tree demo-tree-box"></div>
</div>
</div>
</div>
<div class="layui-col-md10">
<div class="layui-card">
<div class="layui-card-body">
<table class="layui-hide" id="test" lay-filter="test"></table>
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-xs" lay-event="hostAlt">修改</a>
</script>
</div>
</div>
</div>
</div>
</div>
</div>
</div>


JS

先导入tree模块


layui.use(['table','form','layer','tree'], function(){
var table = layui.table;
var layer = layui.layer;
var form = layui.form;
var $ = layui.jquery;
var tree = layui.tree;
}


定义getData函数,用来获取资产节点和主机




function getData() {
var data = [];
$.ajax({
url: '{% url 'host_type_tree_api' %}', //后台数据请求地址
type: "GET",
async: false,
success: function (res) {
data = res.data;
}
});
return data;
}


通过 tree.render() 方法指定一个元素,便可快速创建一个 tree 实例


格式


tree.render({
elem: '#test1'
,click: function(obj){
console.log(obj.data); //得到当前点击的节点数据
console.log(obj.state); //得到当前节点的展开状态:open、close、normal
console.log(obj.elem); //得到当前节点元素
console.log(obj.data.children); //当前节点下是否有子节点
}
});


实现代码


$.ajax({
url: '{% url 'host_type_tree_api' %}',
type: "GET",
async:false,
success: function (res) {
if (res.code ==0){
//树形主机树状使用tree组件
tree.render({
elem: '#treeType'
,data: getData() //数据源
,id: 'treeType' //定义索引
,edit: ['update','del']
//operate操作节点的回调
,operate: function (obj){
var type = obj.type; //节点的操作类型:update,del
var data = obj.data; //得到节点的数据
var elem = obj.elem; //得到当前节点的元素
var host_type_id = data.id; //data.id 获取data上面的唯一索引,传递到后端判断是哪个主机类型需要重命名
var host_type = data.title; //主机类型名称
var host_name_id = data.host_name_id; //data.id 获取data上面的唯一索引,传递到后端判断是哪个主机需要重命名
var host_name = data.title; //主机名称
var data_children = data.children
if (type === 'update'){ //修改主机类型名称
if (data.children == undefined){ //children子节点 没有获取到变量值的话说明修改的是主机类型(父节点)
var children_host_name = "None" //获取children是否有数据,如果没有的话,说明实在修改主机,否则修改主机类型
}else if(data_children.length === 0 ){ //空数组 []判断,就是该主机类型下面没有主机
var children_host_name = data.title
}else {
var children_host_name = data.children[0].title //点击父节点的时候
}
//host_type_id传到后端,根据该id 获取到是哪个主机类型要修改名称
$.post('{% url 'host_type_tree_api' %}',{host_name: host_name,host_name_id: host_name_id,host_type_id:host_type_id,host_type:host_type,children_host_name:children_host_name},function (res){
if (res.code == 0){
layer.msg(res.msg,{icon: 6})
tree.reload('treeType',{data: getData()});
} else if(res.code == 1){
layer.msg(res.msg,{icon: 5})
}
})
} else if(type === 'del'){
if (data.children == undefined){ //children子节点 没有获取到变量值的话说明修改的是主机类型(父节点)
var children_host_name = "None" //获取children是否有数据,如果没有的话,说明实在修改主机,否则修改主机类型
}else if(data_children.length === 0 ){ //空数组 []判断,就是该主机类型下面没有主机
var children_host_name = data.title
}else {
var children_host_name = data.children[0].title //点击父节点的时候
}
var data = {'host_type':host_type,'host_name':host_name,'children_host_name': children_host_name};
$.ajax({
url: '{% url 'host_type_delete' %}',
type: "DELETE",
async:false,
data: data,
success: function (res) {
if (res.code ==0){
layer.msg(res.msg,{icon:6});
tree.reload('treeType',{data: getData()});
}else{
layer.msg(res.msg,{icon:5})
tree.reload('treeType',{data: getData()});
}
},
error: function () {
layer.msg("服务器接口异常",{icon: 5})
}
})
}


}
,onlyIconControl: true //是否仅允许节点左侧图标控制展开收缩
,click: function(obj){

}
});
}else{
layer.msg(res.msg,{icon:5})
}
},
error: function () {
layer.msg("服务器接口异常",{icon: 5})
}
});


设置节点树回调方法

当点击资产树的时候会列出该资产树下面的主机信息;点击主机节点的时候,会列出主机的详情信息。


click: function(obj){
var data = obj.data
var host_name_list = data.children
if (host_name_list == undefined){ //children子节点 没有获取到变量值的话说明修改的是主机类型(父节点)
var host_name = data.title //获取children是否有数据,如果没有的话,说明实在修改主机,否则修改主机类型
var host_name_id = data.id
//定义参数
data = {"host_type":data.title,"host_type_id":data.id,"host_name": host_name,"host_name_id": host_name_id}
table.render({
elem: '#test'
,url:'{% url 'host_type_tree_detail_api' %}'
,skin: 'line'
,where: data
,method: 'GET'
,title: '用户数据表'
,cols: [[
{field: 'host_type', title: '类别', sort: true, width: 160}
,{field: 'host_name', title: '主机名称',sort: true, width: 160}
,{field: 'host_ip', title: '主机IP', sort: true, width: 160}
,{field: 'host_port', title: '端口', sort: true, width: 100}
,{field: 'computer_room_name', title: '机房', sort: true, width: 200}
,{field: 'host_status', title: '状态', sort: true, width: 100}
,{field: 'remarks_info', title: '备注信息', sort: true, width: 220}
]]
,page: true
,id: 'hosttb'
});
}else if(host_name_list.length === 0 ){ //空数组 []判断,就是该主机类型下面没有主机
var host_name = "None"
var host_name_id = "None"
//定义参数
data = {"host_type":data.title,"host_type_id":data.id,"host_name": host_name,"host_name_id": host_name_id}
//layui的table组件接收的数据格式:data:[{},{},....],它是一个数组,每一个元素都是User类型的对象
table.render({
elem: '#test'
,where: data //where传递参数到后端,无需传递参数可以不加
,method: 'GET'
,skin: 'line'
,url:'{% url 'host_type_tree_detail_api' %}'
,title: '用户数据表'
,cols: [[
//第一行不能加逗号,不会表格会不对齐
{field: 'host_type', title: '主机类别',sort: true, width: 160}
,{field: 'host_name', title: '主机',sort: true, width: 160}
,{field: 'remarks_info', title: '备注信息', width: 240}
]]
,page: true
,id: 'hosttypetreetb'
});
}else {
var host_name = "None" //点击父节点的时候
var host_name_id = "None"
//定义参数
data = {"host_type":data.title,"host_type_id":data.id,"host_name": host_name,"host_name_id": host_name_id}


table.render({
elem: '#test'
,where: data //where传递参数到后端,无需传递参数可以不加
,method: 'GET'
,url:'{% url 'host_type_tree_detail_api' %}'
,skin: 'line'
,title: '用户数据表'
,cols: [[
//第一行不能加逗号,不会表格会不对齐
{field: 'host_type', title: '主机类别',sort: true, width: 160}
,{field: 'host_name', title: '主机',sort: true, width: 160}
,{field: 'remarks_info', title: '备注信息', width: 240}
]]
,page: true
,id: 'hosttypetreetb'
});
}
}


完整代码


$.ajax({
url: '{% url 'host_type_tree_api' %}',
type: "GET",
async:false,
success: function (res) {
if (res.code ==0){
//树形主机树状使用tree组件
tree.render({
elem: '#treeType'
,data: getData()


,id: 'treeType'
,edit: ['update','del']
//operate操作节点的回调
,operate: function (obj){
var type = obj.type; //节点的操作类型:update,del
var data = obj.data; //得到节点的数据
var elem = obj.elem; //得到当前节点的元素
var host_type_id = data.id; //data.id 获取data上面的唯一索引,传递到后端判断是哪个主机类型需要重命名
var host_type = data.title; //主机类型名称
var host_name_id = data.host_name_id; //data.id 获取data上面的唯一索引,传递到后端判断是哪个主机需要重命名
var host_name = data.title; //主机名称
var data_children = data.children
if (type === 'update'){ //修改主机类型名称
if (data.children == undefined){ //children子节点 没有获取到变量值的话说明修改的是主机类型(父节点)
var children_host_name = "None" //获取children是否有数据,如果没有的话,说明实在修改主机,否则修改主机类型
}else if(data_children.length === 0 ){ //空数组 []判断,就是该主机类型下面没有主机
var children_host_name = data.title
}else {
var children_host_name = data.children[0].title //点击父节点的时候
}
//host_type_id传到后端,根据该id 获取到是哪个主机类型要修改名称
$.post('{% url 'host_type_tree_api' %}',{host_name: host_name,host_name_id: host_name_id,host_type_id:host_type_id,host_type:host_type,children_host_name:children_host_name},function (res){
if (res.code == 0){
layer.msg(res.msg,{icon: 6})
tree.reload('treeType',{data: getData()});
} else if(res.code == 1){
layer.msg(res.msg,{icon: 5})
}
})
} else if(type === 'del'){
if (data.children == undefined){ //children子节点 没有获取到变量值的话说明修改的是主机类型(父节点)
var children_host_name = "None" //获取children是否有数据,如果没有的话,说明实在修改主机,否则修改主机类型
}else if(data_children.length === 0 ){ //空数组 []判断,就是该主机类型下面没有主机
var children_host_name = data.title
}else {
var children_host_name = data.children[0].title //点击父节点的时候
}
var data = {'host_type':host_type,'host_name':host_name,'children_host_name': children_host_name};
$.ajax({
url: '{% url 'host_type_delete' %}',
type: "DELETE",
async:false,
data: data,
success: function (res) {
if (res.code ==0){
layer.msg(res.msg,{icon:6});
tree.reload('treeType',{data: getData()});
}else{
layer.msg(res.msg,{icon:5})
tree.reload('treeType',{data: getData()});
}
},
error: function () {
layer.msg("服务器接口异常",{icon: 5})
}
})
}


}
,onlyIconControl: true //是否仅允许节点左侧图标控制展开收缩
,click: function(obj){
var data = obj.data
var host_name_list = data.children
if (host_name_list == undefined){ //children子节点 没有获取到变量值的话说明修改的是主机类型(父节点)
var host_name = data.title //获取children是否有数据,如果没有的话,说明实在修改主机,否则修改主机类型
var host_name_id = data.id
//定义参数
data = {"host_type":data.title,"host_type_id":data.id,"host_name": host_name,"host_name_id": host_name_id}
table.render({
elem: '#test'
,url:'{% url 'host_type_tree_detail_api' %}'
,skin: 'line'
,where: data
,method: 'GET'
,title: '用户数据表'
,cols: [[
{field: 'host_type', title: '类别', sort: true, width: 160}
,{field: 'host_name', title: '主机名称',sort: true, width: 160}
,{field: 'host_ip', title: '主机IP', sort: true, width: 160}
,{field: 'host_port', title: '端口', sort: true, width: 100}
,{field: 'computer_room_name', title: '机房', sort: true, width: 200}
,{field: 'host_status', title: '状态', sort: true, width: 100}
,{field: 'remarks_info', title: '备注信息', sort: true, width: 220}
]]
,page: true
,id: 'hosttb'
});
}else if(host_name_list.length === 0 ){ //空数组 []判断,就是该主机类型下面没有主机
var host_name = "None"
var host_name_id = "None"
//定义参数
data = {"host_type":data.title,"host_type_id":data.id,"host_name": host_name,"host_name_id": host_name_id}
//layui的table组件接收的数据格式:data:[{},{},....],它是一个数组,每一个元素都是User类型的对象
table.render({
elem: '#test'
,where: data //where传递参数到后端,无需传递参数可以不加
,method: 'GET'
,skin: 'line'
,url:'{% url 'host_type_tree_detail_api' %}'
,title: '用户数据表'
,cols: [[
//第一行不能加逗号,不会表格会不对齐
{field: 'host_type', title: '主机类别',sort: true, width: 160}
,{field: 'host_name', title: '主机',sort: true, width: 160}
,{field: 'remarks_info', title: '备注信息', width: 240}
]]
,page: true
,id: 'hosttypetreetb'
});
}else {
var host_name = "None" //点击父节点的时候
var host_name_id = "None"
//定义参数
data = {"host_type":data.title,"host_type_id":data.id,"host_name": host_name,"host_name_id": host_name_id}
//layui的table组件接收的数据格式:data:[{},{},....],它是一个数组,每一个元素都是User类型的对象
table.render({
elem: '#test'
,where: data //where传递参数到后端,无需传递参数可以不加
,method: 'GET'
,url:'{% url 'host_type_tree_detail_api' %}'
,skin: 'line'
,title: '用户数据表'
,cols: [[
//第一行不能加逗号,不会表格会不对齐
{field: 'host_type', title: '主机类别',sort: true, width: 160}
,{field: 'host_name', title: '主机',sort: true, width: 160}
,{field: 'remarks_info', title: '备注信息', width: 240}
]]
,page: true
,id: 'hosttypetreetb'
});
}
}
});
}else{
layer.msg(res.msg,{icon:5})
}
},
error: function () {
layer.msg("服务器接口异常",{icon: 5})
}
});


效果

CMDB: 使用LayUI前端框架实现树形资产节点_子节点


CMDB: 使用LayUI前端框架实现树形资产节点_父节点_02


CMDB: 使用LayUI前端框架实现树形资产节点_父节点_03


CMDB: 使用LayUI前端框架实现树形资产节点_子节点_04