授之以渔-运维平台应用模块一(应用树篇)
写在片头:干的是运维工作,爱好动手。纯属个人项目,身兼业务需求人员,产品经理,前端,后端,测试于一体,代码层面会有逻辑问题,请各位看官见谅,下文只是记录我一个10手码农在前端踩过的坑和一些思路。
应用树: 为了方便管理以业务为单位的服务集群,利用树形结构建立起了一种服务组织关系,方便运维日常管理。先上一张效果图:
一、 Jstree
看过XX公司的运维平台的应用树后,从此不能自拔,励志也要写出一样效果的东东。采用的jsTree
,是基于javascript的一个跨浏览器树控件树行,功能强大,最重要的是免费,
左侧的树列表的生成因为会用到mysql的group by,所以采用了redis存储,需要的时候才更新服务树,减少对数据库的压力,项目用到了根据选择树节点点击事件功能。
既点击项目名称的红色图标时,会加载整个项目涉及到的服务器,效果图:
点击项目下单个主机时,会加载这个服务器的应用情况,效果图:
image.png
代码如下:
("changed.jstree", function (e, data) {
var items = data.node.text;
var icon = data.node.icon;
if (icon == "fa fa-building-o font-blue"){
document.getElementById("group1").style.display="none";
document.getElementById("group2").style.display="block";
$.ajax({
type: "GET",
url: "../cmdb_app_info_ajax/?ip="+items,
dataType:'json',
async: false,
beforeSend:function(){
Metronic.blockUI({animate: true});
},
complete: function() {
Metronic.unblockUI();
},
success: function(data){
var sOut= '';
sOut += '<tr>'
sOut += '<td>' + data['设备PHP版本号:'] + '</td>';
sOut += '<td>' + data['设备PHP路径:'] + '</td>';
sOut += '<td>' + data['设备PHP端口号:'] + '</td>';
sOut += '<tr>'
sOut += '<th><b>TOMCAT版本号</b></th>';
sOut += '<th><b>TOMCAT路径</b></th>';
sOut += '<th><b>TOMCAT端口号</b></th>';
sOut += '</tr>'
sOut += '<td>' + data['设备TOMCAT版本号:'] + '</td>';
sOut += '<td>' + data['设备TOMCAT路径:'] + '</td>';
sOut += '<td>' + data['设备TOMCAT端口号:'] + '</td>';
sOut += '<tr>'
sOut += '<th><b>NGINX版本号</b></th>';
sOut += '<th><b>NGINX路径</b></th>';
sOut += '<th><b>NGINX端口号</b></th>';
sOut += '</tr>'
sOut += '<td>' + data['设备NGINX版本号:'] + '</td>';
sOut += '<td>' + data['设备NGINX路径:'] + '</td>';
sOut += '<td>' + data['设备NGINX端口号:'] + '</td>';
sOut += '<tr>'
sOut += '<th><b>MYSQL版本号</b></th>';
sOut += '<th><b>MYSQL路径</b></th>';
sOut += '<th><b>MYSQL端口号</b></th>';
sOut += '<th><b>MYSQL数据库</b></th>';
sOut += '</tr>'
sOut += '<td>' + data['设备MYSQL版本号:'] + '</td>';
sOut += '<td>' + data['设备MYSQL路径:'] + '</td>';
sOut += '<td>' + data['设备MYSQL端口号:'] + '</td>';
sOut += '<td>' + data['设备MYSQL数据库:'] + '</td>';
sOut += '<tr>'
sOut += '<th><b>定时任务</b></th>';
sOut += '</tr>'
sOut += '<td>' + data['设备定时任务:'] + '</td>';
sOut += '</tr>'
$("#cmdb_app_info_get").html(sOut);
},
error: function (data) {
toastr.error('没有数据可以加载')
}
});
}else if (icon == "fa fa-folder icon-state-warning icon-lg"){
document.getElementById("group1").style.display="block";
document.getElementById("group2").style.display="none";
$.ajax({
type: "GET",
url: "../cmdb_subtitle_info_ajax/?subtitle="+items,
dataType:'json',
async: false,
beforeSend:function(){
Metronic.blockUI({animate: true});
},
complete: function() {
Metronic.unblockUI();
},
success: function(data){
if ($('#product_tree').hasClass('dataTable')) {
console.log('重新加载datatable,准备初始化................')
$('#product_tree').dataTable().fnClearTable(false) //清空一下table
$('#product_tree').dataTable().fnDestroy();//还原初始化datatable
}
$("#cmdb_subtitle_info_get").html("");
$.each(data,function() {
var sOut= '';
sOut += '<tr>'
sOut += '<td><input type="checkbox" class="checkboxes" value="1"/></td>'
sOut += '<td id='+this['fields']['unit_title']+'>' + '<a href="../cmdb_assets_list/?place=&status=&year=&type=&model=&search='+ this['fields']['unit_ip'] +'">' + this['fields']['unit_title'] + '</a>' + '</td>';
sOut += '<td>' + this['fields']['unit_subtitle'] + '</td>';
sOut += '<td id='+this['fields']['unit_ip']+'>' + this['fields']['unit_ip'] + '</td>';
sOut += '<td>' + this['fields']['unit_use_type'] + '</td>';
sOut += '<td><a class="fa fa-eye" href="#responsives" data-toggle="modal"></a></td>';
sOut += '</tr>'
$("#cmdb_subtitle_info_get").append(sOut);
});
},
error: function (data) {
toastr.error('没有数据可以加载')
}
});
}
$(document).ready(function(){
eyeClick();
TableManaged.init() //初始化datatables
})
}
捡一些重要的说:
var items = data.node.text; //获取图标名称
var icon = data.node.icon; //获取图标
我是通过判断图标的样式来获取用户点击的是哪里,没办法,谁叫咱是10手码农。同时有group1和gourp2两个tables,选择哪个图标就弹出哪个tables,隐藏另外的,在通过ajax后台获取数据,利用each循环获得的结果,构建出一个tr的内容,最后在添加到tables里,以此实现“山寨版”的数据加载。
这里踩过的坑:
因为我要先加载完数据,然后才是初始化datatables表格。当点击到别的业务图标时,又要经历一次加载数据,然后初始化表格。结果datatables报错了....cannot reinitialise datatable
,大概意思就是datatables不能重复初始化。
最后只能通过判断加载后的tables是否被加载后,如果加载过,先销毁,在初始化。
if ($('#product_tree').hasClass('dataTable')) {
console.log('重新加载datatable,准备初始化................')
$('#product_tree').dataTable().fnClearTable(false) //清空一下table
$('#product_tree').dataTable().fnDestroy();//还原初始化datatable
}
二、 Datatable
右边就是数据展示采用datatables,可以将任何HTML表格添加高级的交互功能,这里用到了checkbox的单选和多选。
datatables代码如下:
var TableManaged = function () {
var initTable2 = function () {
var table = $('#product_tree');
table.dataTable({
"bDestroy": true,
"language": {
"aria": {
"sortAscending": ": activate to sort column ascending",
"sortDescending": ": activate to sort column descending"
},
"emptyTable": "未有相关数据",
"info": "当前显示 _START_ 到 _END_ 条,共 _TOTAL_ 条记录。",
"infoEmpty": "当前显示0到0条,共0条记录",
"infoFiltered": "(数据库中共为 _MAX_ 条记录)",
"lengthMenu": "显示 _MENU_ 记录",
"search": "模糊查询:",
"zeroRecords": "对不起,查询不到任何相关数据",
"oPaginate": {
"sFirst": "首页",
"sPrevious": " 上一页 ",
"sNext": " 下一页 ",
"sLast": " 尾页 "
}
},
"bStateSave": true, // save datatable state(pagination, sort, etc) in cookie.
"lengthMenu": [
[5, 15, 20, -1],
[5, 15, 20, "All"] // change per page values here
],
// set the initial value
"pageLength": 5,
"columnDefs": [{ // set default column settings
'orderable': false,
'targets': [0]
}, {
"searchable": false,
"targets": [0]
}],
"order": [
[1, "asc"]
] // set first column as a default sort by asc
});
var tableWrapper = jQuery('#product_tree_wrapper');
var host_list = [];
var release_build_job = '';
//全选
table.find('.group-checkable').change(function () {
var set = jQuery(this).attr("data-set");
var checked = jQuery(this).is(":checked");
host_list.length = 0;
jQuery(set).each(function () {
if (checked) {
$(this).attr("checked", true);
var hosts = $(this).parent().parent().find('td').eq(1).attr("id");
host_list.push(hosts);
document.getElementById("group3").style.display="block";
} else {
$(this).attr("checked", false);
host_list.length = 0;
document.getElementById("group3").style.display="none";
}
});
jQuery.uniform.update(set);
});
//单选
table.on('change', 'tbody tr .checkboxes', function () {
var hosts = $(this).parent().parent().find('td').eq(1).attr("id");
var checked = jQuery(this).is(":checked");
if (checked) {
$(this).attr("checked", true);
host_list.push(hosts);
if (host_list.length > 1) {
document.getElementById("group3").style.display="block";
}else {
document.getElementById("group3").style.display="none";
}
} else {
$(this).attr("checked", false);
host_list.pop(hosts);
if (host_list.length > 1) {
document.getElementById("group3").style.display="block";
}else {
document.getElementById("group3").style.display="none";
}
}
});
tableWrapper.find('.dataTables_length select').select2(); // initialize select2 dropdown
return {
//main function to initiate the module
init: function () {
if (!jQuery().dataTable) {
return;
}
initTable2();
}
};
}();
捡一些重要的说:
if (host_list.length > 1) {
document.getElementById("group3").style.display="block";
}else {
document.getElementById("group3").style.display="none";
}
判断了checkbox选择的数量,大于1个的话,弹出gourp3(一个批量绘图按钮),效果图如下:
image.png
三、 Multiselect
接上文,这里平台是结合了openfalcon
做的监控,点击上文的监控指标项按钮,会弹出一个multiselect
层,用过openfalcon
的都知道,监控指标多是他的特色之一,所以在选型的时候第一个就想到得需要一个带filter
功能的select
控件,效果图如下:
代码如下:
$('#get_hostlist_counter_select').multiselect({
buttonWidth: '500px',//按钮宽度
nonSelectedText: '---- 请选择监控指标 ----',
nSelectedText: '个被选中',
enableCaseInsensitiveFiltering: true,//不区分大小写
filterPlaceholder: '模糊查询',
enableFiltering: true,
onDropdownShow: function(event) {
get_hostlist_counter()
},
onChange: function (option, checked, select) {
var selectedOptions = $('#get_hostlist_counter_select option:selected');
if (selectedOptions.length >= 6) {
// 禁用选项
toastr.error('监控指标最多同时选取6个')
var nonSelectedOptions = $('#get_hostlist_counter_select option').filter(function() {
return !$(this).is(':selected');
});
nonSelectedOptions.each(function() {
var input = $('input[value="' + $(this).val() + '"]');
input.prop('disabled', true);
input.parent('li').addClass('disabled');
});
}
else {
// 启动选项
$('#get_hostlist_counter_select option').each(function() {
var input = $('input[value="' + $(this).val() + '"]');
input.prop('disabled', false);
input.parent('li').addClass('disabled');
});
}
}
});
捡重要的说:
用到了multiselect
的onDropdownShow
和onChange
两个回调函数,onDropdownShow
可以理解成就是点击菜单下拉事件,触发了get_hostlist_counter()
通过ajax取数据填充options
,代码如下:
function get_hostlist_counter(){
var options_list = []
var obj2 = new Object();
var jsonData ={
'unit_title': host_list.join(','),
};
$("#get_hostlist_counter_select").empty();
$.ajax({
async: false,
type: "POST",
url : "../openfalcon_get_endpoint_counter_ajax/",
data: jsonData,
cache: false,
dataType: "json",
beforeSend:function(){
Metronic.blockUI({animate: true});
},
complete: function() {
Metronic.unblockUI();
},
success: function(obj) {
if (obj['counter'] == ''){
toastr.error('获取监控指标为空或者异常')
}else{
for (var id in obj['counter']){
obj2 = {
label : obj['counter'][id],
value : obj['counter'][id],
};
options_list.push(obj2)
}
$('#get_hostlist_counter_select').multiselect('dataprovider', options_list);
}
}
});
}
onChange
可以理解成点击事件,监控指标这块还是怕传多了,压垮了openfalcon
的graph
的,所以限制最多传6个,超过的话会禁用所有选项。
四、 绘图
最后就是一些绘图了,结合着openfalcon
的API做的,下篇文章再议,效果图: