写在片头:干的是运维工作,爱好动手。纯属个人项目,身兼业务需求人员,产品经理,前端,后端,测试于一体,代码层面会有逻辑问题,请各位看官见谅,下文只是记录我一个10手码农在前端踩过的坑和一些思路。

###应用树: 为了方便管理以业务为单位的服务集群,利用树形结构建立起了一种服务组织关系,方便运维日常管理。先上一张效果图:
授之以渔-运维平台应用模块一(应用树篇)

#一、 Jstree
看过XX公司的运维平台的应用树后,从此不能自拔,励志也要写出一样效果的东东。采用的jsTree,是基于javascript的一个跨浏览器树控件树行,功能强大,最重要的是免费,
左侧的树列表的生成因为会用到mysql的group by,所以采用了redis存储,需要的时候才更新服务树,减少对数据库的压力,项目用到了根据选择树节点点击事件功能。
既点击项目名称的红×××标时,会加载整个项目涉及到的服务器,效果图:
image.png
点击项目下单个主机时,会加载这个服务器的应用情况,效果图:
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控件,效果图如下:
image.png
代码如下:

            $('#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');
                            });
                        }
                    }
            });

捡重要的说:
用到了multiselectonDropdownShowonChange两个回调函数,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可以理解成点击事件,监控指标这块还是怕传多了,压垮了openfalcongraph的,所以限制最多传6个,超过的话会禁用所有选项。
#四、 绘图
最后就是一些绘图了,结合着openfalcon的API做的,下篇文章再议,效果图:
image.png