• CMDB


    资产采集的实现方案

    1. agent模式
    每一台服务器放一份agent程序,subprocess执行采集命令,requests提交数据
    优点:简单,采集速度快
    应用场景:机器多,性能要求降低


    2. ssh模式
    在服务器和API之间放置一台中控机 用ssh远程连接服务器 ,执行命令,获取结果,并发送给API
    应用场景:机器少,性能要求高
    优点:无agent 速度慢 ssh方式
    例如:fabric ansible 封装了paramiko模块 批量执行命令

     3. salt模式

    saltstack(python写的)
    在服务器和API之间放置一台中控机,中控机和服务器上分别安装saltstack,中控机上的salt执行命令获取资产信息
    master
    salve/minion
    应用场景:已经用了saltstack 机器多 比ssh速度快
    原理:

      1 SaltStack 采用`C/S`模式,server端就是salt的master,client端就是minion,minion与master之间通过`ZeroMQ`消息队列通信。
      2 
      3 minion上线后先与master端联系,把自己的`pub key`发过去,这时master端通过`salt-key -L`命令就会看到minion的key,接受该minion-key后,也就是master与minion已经互信。
      4 
      5 master可以发送任何指令让minion执行了,salt有很多可执行模块,比如说cmd模块,在安装minion的时候已经自带了,它们通常位于你的python库中,`locate salt | grep /usr/`可以看到salt自带的所有东西。
      6 
      7 这些模块是python写成的文件,里面会有好多函数,如cmd.run,当我们执行`salt '*' cmd.run 'uptime'`的时候,master下发任务匹配到的minion上去,minion执行模块函数,并返回结果。
      8 
      9 master监听4505和4506端口,4505对应的是ZMQ的PUB system,用来发送消息,4506对应的是REP system是来接受消息的。
     10 具体步骤如下
     11 
     12 ```
     13 1、Salt stack的Master与Minion之间通过ZeroMq进行消息传递,使用了ZeroMq的发布-订阅模式,连接方式包括tcp,ipc
     14 2、salt命令,将cmd.run ls命令从salt.client.LocalClient.cmd_cli发布到master,获取一个Jodid,根据jobid获取命令执行结果。
     15 3、master接收到命令后,将要执行的命令发送给客户端minion。
     16 4、minion从消息总线上接收到要处理的命令,交给minion._handle_aes处理
     17 5、minion._handle_aes发起一个本地线程调用cmdmod执行ls命令。线程执行完ls后,调用minion._return_pub方法,将执行结果通过消息总线返回给master
     18 6、master接收到客户端返回的结果,调用master._handle_aes方法,将结果写的文件中
     19 7、salt.client.LocalClient.cmd_cli通过轮询获取Job执行结果,将结果输出到终端。
     20 ```
     21 
     22 #### saltstack 安装
     23 
     24 [saltstack install](http://repo.saltstack.com/#rhel)
     25 
     26 #### 修改minion配置文件
     27 ```
     28 [root@linux-node2 ~]# vim /etc/salt/minion
     29 master: 192.168.56.11
     30 [root@linux-node2 ~]# vim /etc/salt/minion
     31 master: 192.168.56.11
     32 [root@linux-node1 pki]# pwd
     33 /etc/salt/pki
     34 [root@linux-node1 pki]# tree
     35 .
     36 ├── master
     37 │   ├── master.pem
     38 │   ├── master.pub
     39 │   ├── minions
     40 │   ├── minions_autosign
     41 │   ├── minions_denied
     42 │   ├── minions_pre
     43 │   │   ├── linux-node1.example.com
     44 │   │   └── linux-node2.example.com
     45 │   └── minions_rejected
     46 └── minion
     47     ├── minion_master.pub
     48     ├── minion.pem
     49     └── minion.pub
     50 [root@linux-node1 pki]# salt-key -A
     51 [root@linux-node1 pki]# tree
     52 .
     53 ├── master
     54 │   ├── master.pem
     55 │   ├── master.pub
     56 │   ├── minions
     57 │   │   ├── linux-node1.example.com
     58 │   │   └── linux-node2.example.com
     59 │   ├── minions_autosign
     60 │   ├── minions_denied
     61 │   ├── minions_pre
     62 │   └── minions_rejected
     63 └── minion
     64     ├── minion_master.pub
     65     ├── minion.pem
     66     └── minion.pub
     67 ```
     68 
     69 #### 远程执行
     70 ```
     71 [root@linux-node1 pki]# salt "*" test.ping
     72 linux-node2.example.com:
     73     True
     74 linux-node1.example.com:
     75     True
     76 [root@linux-node1 pki]# salt "*" cmd.run 'w'
     77 linux-node1.example.com:
     78      07:20:24 up 17:10,  1 user,  load average: 0.00, 0.01, 0.05
     79     USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
     80     root     pts/0    192.168.56.1     07:04    0.00s  0.30s  0.26s /usr/bin/python /usr/bin/salt * cmd.run w
     81 linux-node2.example.com:
     82      08:26:25 up 22:40,  2 users,  load average: 0.15, 0.05, 0.06
     83     USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
     84     root     tty1                      Sat09   13:12m  0.02s  0.02s -bash
     85     root     pts/0    192.168.56.1     08:09   13:53   0.04s  0.04s -bash
     86 ```
     87 
     88 #### 配置管理
     89 ##### YAML
     90 
     91 - 缩进:
     92   - 两个空格
     93   - 不能使用tab键
     94   - 缩进代表层级关系
     95 
     96 - 冒号:
     97   - key: value
     98 
     99 - 短横线代表list
    100 
    101 #### satate模块
    102 ```
    103 # vim /etc/salt/master
    104 file_roots:
    105   base:
    106     - /srv/salt
    107 # mkdir /srv/salt
    108 # mkdir /srv/salt
    109 # cd /srv/salt
    110 # mkdir web
    111 # cd web
    112 # pwd
    113 /srv/salt/web
    114 # vim apache.sls
    115 apache-install:
    116   pkg.installed:
    117     - names:
    118       - httpd
    119       - httpd-devel
    120 
    121 apache-service:
    122   service.running:
    123     - name: httpd
    124     - enable: True
    125 
    126 # salt '*' state.sls web.apache
    127  [root@linux-node2 salt]# cd /var/cache/salt/
    128 [root@linux-node2 salt]# tree               
    129 .
    130 `-- minion
    131     |-- extmods
    132     |-- files
    133     |   `-- base
    134     |       `-- web
    135     |           `-- apache.sls
    136     |-- pkg_refresh
    137     `-- proc
    138         `-- 20160605081351939477 
    139 # cat /var/cache/salt/minion/files/base/web/apache.sls 
    140 apache-install:
    141   pkg.installed:
    142     - names:
    143       - httpd
    144       - httpd-devel
    145 
    146 apache-service:
    147   service.running:
    148     - name: httpd
    149     - enable: True   
    150 # ps -ef|grep yum
    151 root      34129  34103  1 08:13 ?        00:00:00 /usr/bin/python /usr/bin/yum --quiet check-update
    152 root      34204  34149  0 08:14 pts/1    00:00:00 grep --color=auto yum
    153 
    154 # cd /srv/salt/
    155 # vim top.sls
    156 base:
    157   'linux-node1.example.com':
    158     - web.apache
    159   'linux-node2.example.com':
    160     - web.apache
    161 # salt '*' state.highstate test=True
    162 # salt '*' state.highstate
    163 
    164 # lsof -i:4505 -n
    165 COMMAND     PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
    166 salt-mast 24739 root   13u  IPv4 4637762      0t0  TCP *:4505 (LISTEN)
    167 salt-mast 24739 root   15u  IPv4 4640421      0t0  TCP 192.168.56.11:4505->192.168.56.11:48344 (ESTABLISHED)
    168 salt-mast 24739 root   16u  IPv4 4640542      0t0  TCP 192.168.56.11:4505->192.168.56.12:53039 (ESTABLISHED)
    169 salt-mini 25378 root   25u  IPv4 4640888      0t0  TCP 192.168.56.11:48344->192.168.56.11:4505 (ESTABLISHED)
    170 ```
    171 
    172 #### 数据系统
    173 
    174 ##### Grains
    175 静态数据 当minion启动时收集的minion本地相关信息
    View Code

    4. puppet(ruby)每30分钟连接一次master,执行一次ruby脚本

    场景:公司现在在使用puppet

     代码流程:

    资产采集部分
    
         采集资产subprocess,
         兼容性(agent,ssh,salt),
         正则或字符串方法(插件)
    
    
    1 配置文件:默认配置和自定义配置
    
    2 开发可插拔插件(每个公司采集的资产信息不同)
     
      配置--路径--对应插件(中间件的设计模式)
      插件-反射----init文件(从配置文件中获取插件信息)--pluginmanage (获取和执行插件)
      给插件设置统一的方法process (返回对应信息)
    
    3 解决兼容问题
      方法一:设置基类(做扩展时麻烦)
      方法二:给process传参 commond     在commod函数中先做判断
    
    4    插件的构造方法执行之前自定制一些操作
         @classmethod
         def initial(cls)
            .....
            return cls()
         错误堆栈信息:try  except
    
         测试模式:debug
          
    5 向API发送数据
      从API获取未采集资产
    View Code

    问题:

    唯一标识:

    周期:2-3个月,3个人
    你负责做什么?
      3处借鉴了Django源码的设计模式:
       
         1 默认配置和自定义配置
         2 中间件---插件做成可插拔的模式,增加采集资源的插件时,只要写一个类(命令+结果格式化)
                  在配置文件中写上路径,就可以采集资源的信息
                 用到了反射
      遇到的难题:唯一标识
    
    
    1  唯一标识
          所有物理硬件上的标识不能作为唯一标识
               主板SN号:虚拟机的SN号可能相同
               IP地址会变
               Mac地址不准确
     标准化:
       --主机名不重复,作为唯一标识
       --流程标准化
                --资产录入,机房,机柜,机柜位置
                --装机时,需要将服务信息录入CMDB
                --资产采集   
    
     最终流程:标准化:主机名不重复。流程标准化:装机同时,主机名在cmdb中设置
    
     步骤:
       agent:
           a. 装系统,初始化软件cmdb,运行cmdb
                   --通过命令获取主机名
                   --写入本地指定文件
           b. 将资产信息发送到API
           c.获取资产信息
                  -本地文件主机名!= 命令获取的主机名(按照文件中的主机名)
                  -本地文件住居明==命令获取的文件主机名
       ssh/salt:
           中控机:获取未采集主机名列表
    View Code

    线程池:

     1 2 线程池
     2   提高并发
     3   python2: 进程池
     4   python3:线程池 进程池
     5   代码示例:
     6      from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
     7     
     8      def task(i)
     9          print(i)
    10 
    11      p=ThreadPoolExecutor(10)
    12      for row in range(100)
    13           p.submit(task,row)
    View Code

     API验证:


    为什么要做API验证?
            数据传输过程中,保证数据不被篡改
    如何设计?
                --和Tornado中的加密Cookie类似
                --客户端创建动态key md5(key+time)|time
                --服务端 添加限制
                        -- 时间限制
                        -- 算法规则限制
                        -- 已访问记录 2s
                      ps :黑客窃取数据后,速度比正常汇报速度快,解决方法:数据加密 crypto模块 AES

     1 #API验证
     2 # 发令牌  静态, 隐患:易被他人截取
     3 
     4 import requests
     5 key='ncjfsvnjsflbvfjslgbvhglbhfbh'
     6 response=requests.get('http://127.0.0.1:8000/api/asset/',headers={'openkey':key})
     7 print(response.text)
     8 
     9 # 改良 动态令牌   隐患:易被他人截取
    10 import time
    11 import requests
    12 import hashlib
    13 #
    14 ctime=time.time()
    15 key='vmkdsf;nvfglnbglbngjflbn'
    16 new_key='%s|%s'%(key,ctime)
    17 
    18 m=hashlib.md5()
    19 m.update(bytes(new_key,encoding='utf8'))
    20 md5_key=m.hexdigest()
    21 
    22 md5_time_key='%s|%s'%(md5_key,ctime)
    23 response=requests.get('http://127.0.0.1:8000/api/asset/',headers={'openkey':md5_time_key})
    24 print(response.text)
    25 
    26 #解决方法
    27 #   ---记录已发送的md5_time_key
    28 #   ---时间限制:将10s以外的排除
    29 #   将两个限制结合起来
    30 
    31 #隐患:黑客网速快
    32 # 不管:搭建内网
    33 # 管:数据加密
    研究过程

    最终代码

     1     def post_asset(self,server_info):
     2         #数据加密
     3         server_info=json.dumps(server_info)
     4         server_info=self.xxxxxx(server_info)
     5         #API验证
     6         ctime=time.time()
     7         key='vmkdsf;nvfglnbglbngjflbn'
     8         new_key='%s|%s'%(key,ctime)
     9 
    10         m=hashlib.md5()
    11         m.update(bytes(new_key,encoding='utf8'))
    12         md5_key=m.hexdigest()
    13 
    14         md5_time_key='%s|%s'%(md5_key,ctime)
    15 
    16         response=requests.get(
    17             url=settings.API,
    18             headers={'openkey':md5_time_key,'Content-Type':'application/json'},
    19             data=server_info)
    20         # response = requests.get(settings.API,headers={'openkey': md5_time_key,},json=server_info)
    21         return response.text
    客户端
     1 def api_confirm(func):
     2     def wrapper(request):
     3             api_key_record = {}
     4             client_md5_time_key = request.META.get('HTTP_OPENKEY')
     5             client_md5_key, client_time = client_md5_time_key.split('|')
     6             client_time = float(client_time)
     7             server_time = time.time()
     8 
     9             # 第一关 排除超过10秒的请求
    10             if server_time - client_time > 10:
    11                 return HttpResponse('你网络太慢了吧,重新发')
    12             # 第二关:匹配MD5值
    13 
    14             temp = "%s|%s" % (settings.AUTH_KEY, client_time,)
    15             m = hashlib.md5()
    16             m.update(bytes(temp, encoding='utf-8'))
    17             server_md5_key = m.hexdigest()
    18             if server_md5_key != client_md5_key:
    19                 return HttpResponse('修改时间了吧,你还嫩点')
    20 
    21             # 将过期的MD5值记录删除
    22             for k in list(api_key_record.keys()):
    23                 v = api_key_record[k]
    24                 if server_time > v:
    25                     del api_key_record[k]
    26 
    27             # 第三关:检查此MD5值10秒之内是否访问过
    28             if client_md5_time_key in api_key_record:
    29                 return HttpResponse('是你,是你,就是你,heck')
    30             else:
    31                 api_key_record[client_md5_time_key] = client_time + 10
    32             return func(request)
    33     return wrapper
    服务端(装饰器)

    数据库设计 

      1 from django.db import models
      2 
      3 
      4 class UserProfile(models.Model):
      5     """
      6     用户信息
      7     """
      8     name = models.CharField(u'姓名', max_length=32)
      9     email = models.EmailField(u'邮箱')
     10     phone = models.CharField(u'座机', max_length=32)
     11     mobile = models.CharField(u'手机', max_length=32)
     12 
     13     class Meta:
     14         verbose_name_plural = "用户表"
     15 
     16     def __str__(self):
     17         return self.name
     18 
     19 
     20 class AdminInfo(models.Model):
     21     """
     22     用户登陆相关信息
     23     """
     24     user_info = models.OneToOneField("UserProfile")
     25     username = models.CharField(u'用户名', max_length=64)
     26     password = models.CharField(u'密码', max_length=64)
     27 
     28     class Meta:
     29         verbose_name_plural = "管理员表"
     30 
     31     def __str__(self):
     32         return self.user_info.name
     33 
     34 
     35 class UserGroup(models.Model):
     36     """
     37     用户组
     38     """
     39     name = models.CharField(max_length=32, unique=True)
     40     users = models.ManyToManyField('UserProfile')
     41 
     42     class Meta:
     43         verbose_name_plural = "用户组表"
     44 
     45     def __str__(self):
     46         return self.name
     47 
     48 
     49 class BusinessUnit(models.Model):
     50     """
     51     业务线
     52     """
     53     name = models.CharField('业务线', max_length=64, unique=True)
     54     contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c')
     55     manager = models.ForeignKey('UserGroup', verbose_name='系统管理员', related_name='m')
     56 
     57     class Meta:
     58         verbose_name_plural = "业务线表"
     59 
     60     def __str__(self):
     61         return self.name
     62 
     63 
     64 class IDC(models.Model):
     65     """
     66     机房信息
     67     """
     68     name = models.CharField('机房', max_length=32)
     69     floor = models.IntegerField('楼层', default=1)
     70 
     71     class Meta:
     72         verbose_name_plural = "机房表"
     73 
     74     def __str__(self):
     75         return self.name
     76 
     77 
     78 class Tag(models.Model):
     79     """
     80     资产标签
     81     """
     82     name = models.CharField('标签', max_length=32, unique=True)
     83 
     84     class Meta:
     85         verbose_name_plural = "标签表"
     86 
     87     def __str__(self):
     88         return self.name
     89 
     90 
     91 class Asset(models.Model):
     92     """
     93     资产信息表,所有资产公共信息(交换机,服务器,防火墙等)
     94     """
     95     device_type_choices = (
     96         (1, '服务器'),
     97         (2, '交换机'),
     98         (3, '防火墙'),
     99     )
    100     device_status_choices = (
    101         (1, '上架'),
    102         (2, '在线'),
    103         (3, '离线'),
    104         (4, '下架'),
    105     )
    106 
    107     device_type_id = models.IntegerField(choices=device_type_choices, default=1)
    108     device_status_id = models.IntegerField(choices=device_status_choices, default=1)
    109 
    110     cabinet_num = models.CharField('机柜号', max_length=30, null=True, blank=True)
    111     cabinet_order = models.CharField('机柜中序号', max_length=30, null=True, blank=True)
    112 
    113     idc = models.ForeignKey('IDC', verbose_name='IDC机房', null=True, blank=True)
    114     business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True)
    115 
    116     tag = models.ManyToManyField('Tag')
    117 
    118     latest_date = models.DateField(null=True)
    119     create_at = models.DateTimeField(auto_now_add=True)
    120 
    121     class Meta:
    122         verbose_name_plural = "资产表"
    123 
    124     # def __str__(self):
    125     #     return "%s-%s-%s" % (self.idc.name, self.cabinet_num, self.cabinet_order)
    126 
    127 
    128 class Server(models.Model):
    129     """
    130     服务器信息
    131     """
    132     asset = models.OneToOneField('Asset')
    133 
    134     hostname = models.CharField(max_length=128, unique=True)
    135     sn = models.CharField('SN号', max_length=64, db_index=True)
    136     manufacturer = models.CharField(verbose_name='制造商', max_length=64, null=True, blank=True)
    137     model = models.CharField('型号', max_length=64, null=True, blank=True)
    138 
    139     manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True)
    140 
    141     os_platform = models.CharField('系统', max_length=16, null=True, blank=True)
    142     os_version = models.CharField('系统版本', max_length=16, null=True, blank=True)
    143 
    144     cpu_count = models.IntegerField('CPU个数', null=True, blank=True)
    145     cpu_physical_count = models.IntegerField('CPU物理个数', null=True, blank=True)
    146     cpu_model = models.CharField('CPU型号', max_length=128, null=True, blank=True)
    147 
    148     create_at = models.DateTimeField(auto_now_add=True, blank=True)
    149 
    150     class Meta:
    151         verbose_name_plural = "服务器表"
    152 
    153     def __str__(self):
    154         return self.hostname
    155 
    156 
    157 class NetworkDevice(models.Model):
    158     asset = models.OneToOneField('Asset')
    159     management_ip = models.CharField('管理IP', max_length=64, blank=True, null=True)
    160     vlan_ip = models.CharField('VlanIP', max_length=64, blank=True, null=True)
    161     intranet_ip = models.CharField('内网IP', max_length=128, blank=True, null=True)
    162     sn = models.CharField('SN号', max_length=64, unique=True)
    163     manufacture = models.CharField(verbose_name=u'制造商', max_length=128, null=True, blank=True)
    164     model = models.CharField('型号', max_length=128, null=True, blank=True)
    165     port_num = models.SmallIntegerField('端口个数', null=True, blank=True)
    166     device_detail = models.CharField('设置详细配置', max_length=255, null=True, blank=True)
    167 
    168     class Meta:
    169         verbose_name_plural = "网络设备"
    170 
    171 
    172 class Disk(models.Model):
    173     """
    174     硬盘信息
    175     """
    176     slot = models.CharField('插槽位', max_length=8)
    177     model = models.CharField('磁盘型号', max_length=32)
    178     capacity = models.FloatField('磁盘容量GB')
    179     pd_type = models.CharField('磁盘类型', max_length=32)
    180     server_obj = models.ForeignKey('Server',related_name='disk')
    181 
    182     class Meta:
    183         verbose_name_plural = "硬盘表"
    184 
    185     def __str__(self):
    186         return self.slot
    187 
    188 
    189 class NIC(models.Model):
    190     """
    191     网卡信息
    192     """
    193     name = models.CharField('网卡名称', max_length=128)
    194     hwaddr = models.CharField('网卡mac地址', max_length=64)
    195     netmask = models.CharField(max_length=64)
    196     ipaddrs = models.CharField('ip地址', max_length=256)
    197     up = models.BooleanField(default=False)
    198     server_obj = models.ForeignKey('Server',related_name='nic')
    199 
    200 
    201     class Meta:
    202         verbose_name_plural = "网卡表"
    203 
    204     def __str__(self):
    205         return self.name
    206 
    207 
    208 class Memory(models.Model):
    209     """
    210     内存信息
    211     """
    212     slot = models.CharField('插槽位', max_length=32)
    213     manufacturer = models.CharField('制造商', max_length=32, null=True, blank=True)
    214     model = models.CharField('型号', max_length=64)
    215     capacity = models.FloatField('容量', null=True, blank=True)
    216     sn = models.CharField('内存SN号', max_length=64, null=True, blank=True)
    217     speed = models.CharField('速度', max_length=16, null=True, blank=True)
    218 
    219     server_obj = models.ForeignKey('Server',related_name='memory')
    220 
    221 
    222     class Meta:
    223         verbose_name_plural = "内存表"
    224 
    225     def __str__(self):
    226         return self.slot
    227 
    228 
    229 class AssetRecord(models.Model):
    230     """
    231     资产变更记录,creator为空时,表示是资产汇报的数据。
    232     """
    233     asset_obj = models.ForeignKey('Asset', related_name='ar')
    234     content = models.TextField(null=True)# 新增硬盘
    235     creator = models.ForeignKey('UserProfile', null=True, blank=True)
    236     create_at = models.DateTimeField(auto_now_add=True)
    237 
    238 
    239     class Meta:
    240         verbose_name_plural = "资产记录表"
    241 
    242     def __str__(self):
    243         return "%s-%s-%s" % (self.asset_obj.idc.name, self.asset_obj.cabinet_num, self.asset_obj.cabinet_order)
    244 
    245 
    246 class ErrorLog(models.Model):
    247     """
    248     错误日志,如:agent采集数据错误 或 运行错误
    249     """
    250     asset_obj = models.ForeignKey('Asset', null=True, blank=True)
    251     title = models.CharField(max_length=16)
    252     content = models.TextField()
    253     create_at = models.DateTimeField(auto_now_add=True)
    254 
    255     class Meta:
    256         verbose_name_plural = "错误日志表"
    257 
    258     def __str__(self):
    259         return self.title
    View Code

    数据展示层

     1     <div>
     2         <div class="search-list clearfix" style="position: relative">
     3 
     4             <div class="search-btn col-md-offset-9 col-md-3" style="position: absolute;bottom: 1px;text-align: left" >
     5                 <input id="doSearch" type="button" class="btn btn-primary" value="搜索" />
     6             </div>
     7 
     8             <div class="search-item col-md-offset-2 col-md-10 clearfix" style="position: relative;height: 35px;">
     9                 <div style="position: absolute;left:0; 38px;">
    10                     <a type="button" class="btn btn-default add-search-condition">
    11                         <span class="glyphicon glyphicon-plus"></span>
    12                     </a>
    13                 </div>
    14                 <div class="input-group searchArea" style="position: absolute;left: 40px;right:300px;">
    15                     <div class="input-group-btn">
    16                         <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
    17                             <span class="searchDefault">默认值</span>
    18                             <span class="caret"></span>
    19                         </button>
    20                         <ul class="dropdown-menu">
    21 
    22                         </ul>
    23                     </div>
    24                     <!-- /btn-group -->
    25                     <!-- <input type="text" class="form-control" aria-label="..."> -->
    26 
    27                 </div>
    28             </div>
    29 
    30         </div>
    31     </div>
    组合搜索框
     1 </head>
     2 <body>
     3    <div style=" 700px;margin: 0 auto">
     4        <div class="btn-group" role="group" aria-label="..." style="margin: 20px">
     5           <button id="checkAll" type="button" class="btn btn-default">全选</button>
     6           <button id="checkReverse" type="button" class="btn btn-default">反选</button>
     7           <button id="checkCancel" type="button" class="btn btn-default">取消</button>
     8           <button id="inOutEditMode" type="button" class="btn btn-default">进入编辑模式</button>
     9           <a class="btn btn-default" href="#">添加</a>
    10           <button id="multiDel" type="button" class="btn btn-default">删除</button>
    11           <button id="refresh" type="button" class="btn btn-default">刷新</button>
    12           <button id="save" type="button" class="btn btn-default">保存</button>
    13         </div>
    14         <table class="table table-bordered table-striped">
    15             <thead id="tbHead">
    16                 <tr>
    17 
    18                 </tr>
    19             </thead>
    20             <tbody id="tbBody">
    21 
    22             </tbody>
    23         </table>
    24     </div>
    25 </body>
    26 </html>
    增删改查导航框
      1 (function (jq) {
      2     var CREATE_SEARCH_CONDITION = true;
      3     var GLOBAL_DICT = {};
      4     /*
      5     {
      6         'device_type_choices': (
      7                                     (1, '服务器'),
      8                                     (2, '交换机'),
      9                                     (3, '防火墙'),
     10                                 )
     11         'device_status_choices': (
     12                                     (1, '上架'),
     13                                     (2, '在线'),
     14                                     (3, '离线'),
     15                                     (4, '下架'),
     16                                 )
     17     }
     18      */
     19 
     20     // 为字符串创建format方法,用于字符串格式化
     21     String.prototype.format = function (args) {
     22         return this.replace(/{(w+)}/g, function (s, i) {
     23             return args[i];
     24         });
     25     };
     26 
     27     function getSearchCondition(){
     28         var condition = {};
     29         $('.search-list').find('input[type="text"],select').each(function(){
     30 
     31             /* 获取所有搜索条件 */
     32             var name = $(this).attr('name');
     33             var value = $(this).val();
     34             if(condition[name]){
     35                 condition[name].push(value);
     36             }else{
     37                 condition[name] = [value];
     38             }
     39 
     40         });
     41         return condition;
     42     }
     43 
     44     function initial(url) {
     45         // 执行一个函数, 获取当前搜索条件
     46         var searchCondition = getSearchCondition();
     47         console.log(searchCondition);
     48         $.ajax({
     49             url: url,
     50             type: 'GET',  // 获取数据
     51             data: {condition: JSON.stringify(searchCondition)},
     52             dataType: 'JSON',
     53             success: function (arg) {
     54                 $.each(arg.global_dict,function(k,v){
     55                      GLOBAL_DICT[k] = v
     56                 });
     57                 initTableHeader(arg.table_config);
     58                 initTableBody(arg.server_list, arg.table_config);
     59                 initSearch(arg.search_config);
     60             }
     61         })
     62     }
     63 
     64     /*
     65     初始化搜索条件
     66      */
     67     function initSearch(searchConfig){
     68         if(searchConfig && CREATE_SEARCH_CONDITION){
     69 
     70             CREATE_SEARCH_CONDITION = false;
     71             // 找打searchArea ul,
     72             $.each(searchConfig,function(k,v){
     73                 var li = document.createElement('li');
     74                 $(li).attr('search_type', v.search_type);
     75                 $(li).attr('name', v.name);
     76                 if(v.search_type == 'select'){
     77                      $(li).attr('global_name', v.global_name);
     78                 }
     79 
     80                 var a = document.createElement('a');
     81                 a.innerHTML = v.text;
     82                 $(li).append(a);
     83                 $('.searchArea ul').append(li);
     84             });
     85 
     86             // 初始化默认搜索条件
     87             // searchConfig[0],进行初始化
     88             // 初始化默认选中值
     89             $('.search-item .searchDefault').text(searchConfig[0].text);
     90             if(searchConfig[0].search_type == 'select'){
     91                 var sel = document.createElement('select');
     92                 $(sel).attr('class','form-control');
     93                 $.each(GLOBAL_DICT[searchConfig[0].global_name],function(k,v){
     94                     var op = document.createElement('option');
     95                     $(op).text(v[1]);
     96                     $(op).val(v[0]);
     97                     $(sel).append(op)
     98                 });
     99                 $('.input-group').append(sel);
    100             }else{
    101                 // <input type="text" class="form-control" aria-label="...">
    102                 var inp = document.createElement('input');
    103                 $(inp).attr('name',searchConfig[0].name);
    104                 $(inp).attr('type','text');
    105                 $(inp).attr('class','form-control');
    106                 $('.input-group').append(inp);
    107             }
    108 
    109 
    110         }
    111     }
    112 
    113     function initTableHeader(tableConfig) {
    114         /*
    115          [
    116          {'q':'id','title':'ID'},
    117          {'q':'hostname','title':'主机名'},
    118          ]
    119          */
    120         $('#tbHead').empty();
    121         var tr = document.createElement('tr');
    122         $.each(tableConfig, function (k, v) {
    123             if (v.display) {
    124                 var tag = document.createElement('th');
    125                 tag.innerHTML = v.title;
    126                 $(tr).append(tag);
    127             }
    128         });
    129         $('#tbHead').append(tr);
    130     }
    131 
    132     function initTableBody(serverList, tableConfig) {
    133         /*
    134          serverList = [
    135          {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    136          {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    137          {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    138          {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    139          ]
    140          */
    141         $('#tbBody').empty();
    142         $.each(serverList, function (k, row) {
    143             // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
    144             /*
    145              <tr>
    146              <td>id</td>
    147              <td>hostn</td>
    148              <td>create</td>
    149              </tr>
    150              */
    151             var tr = document.createElement('tr');
    152             tr.setAttribute('nid',row.id);
    153             $.each(tableConfig, function (kk, rrow) {
    154                 // kk: 1  rrow:{'q':'id','title':'ID'},         // rrow.q = "id"
    155                 // kk: .  rrow:{'q':'hostname','title':'主机名'},// rrow.q = "hostname"
    156                 // kk: .  rrow:{'q':'create_at','title':'创建时间'}, // rrow.q = "create_at"
    157                 if (rrow.display) {
    158                     var td = document.createElement('td');
    159 
    160                     /* 在td标签中添加内容 */
    161                     var newKwargs = {}; // {'n1':'1','n2':'123'}
    162                     $.each(rrow.text.kwargs, function (kkk, vvv) {
    163                         var av = vvv;
    164                         if(vvv.substring(0,2) == '@@'){
    165                             var global_dict_key = vvv.substring(2,vvv.length);
    166                             var nid = row[rrow.q];
    167                             $.each(GLOBAL_DICT[global_dict_key],function(gk,gv){
    168                                 if(gv[0] == nid){
    169                                     av = gv[1];
    170                                 }
    171                             })
    172                         }
    173                         else if (vvv[0] == '@') {
    174                             av = row[vvv.substring(1, vvv.length)];
    175                         }
    176                         newKwargs[kkk] = av;
    177                     });
    178                     var newText = rrow.text.tpl.format(newKwargs);
    179                     td.innerHTML = newText;
    180 
    181                     /* 在td标签中添加属性 */
    182                     $.each(rrow.attrs,function(atkey,atval){
    183                         // 如果@
    184                         if (atval[0] == '@') {
    185                             td.setAttribute(atkey, row[atval.substring(1, atval.length)]);
    186                         }else{
    187                             td.setAttribute(atkey,atval);
    188                         }
    189                     });
    190 
    191                     $(tr).append(td);
    192                 }
    193             });
    194             $('#tbBody').append(tr);
    195 
    196         })
    197     }
    198 
    199     function trIntoEdit($tr){
    200         $tr.find('td[edit-enable="true"]').each(function(){
    201             // $(this) 每一个td
    202             var editType = $(this).attr('edit-type');
    203             if(editType == 'select'){
    204                 // 生成下拉框:找到数据源
    205                 var deviceTypeChoices = GLOBAL_DICT[$(this).attr('global_key')];
    206 
    207                 // 生成select标签
    208                 var selectTag = document.createElement('select');
    209                 var origin = $(this).attr('origin');
    210 
    211                 $.each(deviceTypeChoices,function(k,v){
    212                     var option = document.createElement('option');
    213                     $(option).text(v[1]);
    214                     $(option).val(v[0]);
    215                     if(v[0] == origin){
    216                         // 默认选中原来的值
    217                         $(option).prop('selected',true);
    218                     }
    219                     $(selectTag).append(option);
    220                 });
    221 
    222                 $(this).html(selectTag);
    223                 // 显示默认值
    224             }else{
    225                 // 获取原来td中的文本内容
    226                 var v1 = $(this).text();
    227                 // 创建input标签,并且内部设置值
    228                 var inp = document.createElement('input');
    229                 $(inp).val(v1);
    230                 // 添加到td中
    231                 $(this).html(inp);
    232             }
    233 
    234 
    235         });
    236     }
    237     function trOutEdit($tr){
    238         $tr.find('td[edit-enable="true"]').each(function(){
    239             // $(this) 每一个td
    240             var editType = $(this).attr('edit-type');
    241             if(editType == 'select'){
    242                 var option = $(this).find('select')[0].selectedOptions;
    243                 $(this).attr('new-origin',$(option).val());
    244                 $(this).html($(option).text());
    245             }else{
    246                 var inputVal = $(this).find('input').val();
    247                 $(this).html(inputVal);
    248             }
    249 
    250         });
    251     }
    252     jq.extend({
    253         xx: function (url) {
    254             initial(url);
    255 
    256             // 所有checkbox绑定事件
    257             $('#tbBody').on('click',':checkbox',function(){
    258                 // $(this) // checkbox标签
    259                 // 1. 检测是否已经被选中
    260                 if($('#inOutEditMode').hasClass('btn-warning')){
    261                     var $tr = $(this).parent().parent();
    262                     if($(this).prop('checked')){
    263                         // 进入编辑模式
    264                         trIntoEdit($tr);
    265                     }else{
    266                         // 退出编辑模式
    267                         trOutEdit($tr);
    268                     }
    269                 }
    270             });
    271 
    272             // 所有按钮绑定事件
    273             $('#checkAll').click(function(){
    274                 if($('#inOutEditMode').hasClass('btn-warning')){
    275                     $('#tbBody').find(':checkbox').each(function(){
    276                         if(!$(this).prop('checked')){
    277                             var $tr = $(this).parent().parent();
    278                             trIntoEdit($tr);
    279                             $(this).prop('checked',true);
    280                         }
    281                     })
    282                 }else{
    283                     $('#tbBody').find(':checkbox').prop('checked',true);
    284                 }
    285 
    286             });
    287 
    288             $('#checkReverse').click(function(){
    289                 if($('#inOutEditMode').hasClass('btn-warning')){
    290                     $('#tbBody').find(':checkbox').each(function(){
    291                         var $tr = $(this).parent().parent();
    292                         if($(this).prop('checked')){
    293                             trOutEdit($tr);
    294                             $(this).prop('checked',false);
    295                         }else{
    296                             trIntoEdit($tr);
    297                             $(this).prop('checked',true);
    298                         }
    299                     })
    300                 }else{
    301                     $('#tbBody').find(':checkbox').each(function(){
    302                         var $tr = $(this).parent().parent();
    303                         if($(this).prop('checked')){
    304                             $(this).prop('checked',false);
    305                         }else{
    306                             $(this).prop('checked',true);
    307                         }
    308                     })
    309                 }
    310             });
    311 
    312             $('#checkCancel').click(function(){
    313                 if($('#inOutEditMode').hasClass('btn-warning')){
    314                     $('#tbBody').find(':checkbox').each(function(){
    315                         if($(this).prop('checked')){
    316                             var $tr = $(this).parent().parent();
    317                             trOutEdit($tr);
    318                             $(this).prop('checked',false);
    319                         }
    320                     })
    321                 }else{
    322                     $('#tbBody').find(':checkbox').prop('checked',false);
    323                 }
    324             });
    325 
    326             $('#inOutEditMode').click(function(){
    327                 if($(this).hasClass('btn-warning')){
    328                     // 退出编辑模式
    329                     $(this).removeClass('btn-warning');
    330                     $(this).text('进入编辑模式');
    331                     $('#tbBody').find(':checkbox').each(function(){
    332                         if($(this).prop('checked')){
    333                             var $tr = $(this).parent().parent();
    334                             trOutEdit($tr);
    335                         }
    336                     })
    337                 }else{
    338                     // 进入编辑模式
    339                     $(this).addClass('btn-warning');
    340                     $(this).text('退出编辑模式');
    341 
    342                     $('#tbBody').find(':checkbox').each(function(){
    343                         if($(this).prop('checked')){
    344                             var $tr = $(this).parent().parent();
    345                             trIntoEdit($tr);
    346                         }
    347                     })
    348                 }
    349             });
    350 
    351             $('#multiDel').click(function(){
    352                 // $('#tbBody').find(':checkbox')
    353                 var idList = [];
    354                 $('#tbBody').find(':checked').each(function(){
    355                     var v = $(this).val();
    356                     idList.push(v)
    357                 });
    358 
    359                 $.ajax({
    360                     url: url,
    361                     type: 'delete',
    362                     data: JSON.stringify(idList),
    363                     success:function(arg){
    364                         console.log(arg);
    365                     }
    366                 })
    367 
    368             });
    369 
    370             $('#refresh').click(function(){
    371                 initial(url)
    372             });
    373 
    374             $('#save').click(function(){
    375                 if($('#inOutEditMode').hasClass('btn-warning')){
    376 
    377                      $('#tbBody').find(':checkbox').each(function(){
    378                         if($(this).prop('checked')){
    379                             var $tr = $(this).parent().parent();
    380                             trOutEdit($tr);
    381                         }
    382                     })
    383 
    384                 }
    385 
    386                 var all_list = [];
    387                 // 获取用户修改过的数据
    388                 $('#tbBody').children().each(function(){
    389                     // $(this) = tr
    390                     var $tr= $(this);
    391                     var nid= $tr.attr('nid');
    392                     var row_dict = {};
    393                     var flag = false;
    394                     $tr.children().each(function(){
    395                         if($(this).attr('edit-enable')) {
    396                             if($(this).attr('edit-type') == 'select'){
    397                                 var newData = $(this).attr('new-origin');
    398                                 var oldData = $(this).attr('origin');
    399                                 if(newData){
    400                                     if (newData != oldData) {
    401                                         var name = $(this).attr('name');
    402                                         row_dict[name] = newData;
    403                                         flag = true;
    404                                     }
    405                                 }
    406 
    407                             }else{
    408                                 var newData = $(this).text();
    409                                 var oldData = $(this).attr('origin');
    410                                 if (newData != oldData) {
    411                                     var name = $(this).attr('name');
    412                                     row_dict[name] = newData;
    413                                     flag = true;
    414                                 }
    415                             }
    416 
    417                         }
    418 
    419                     });
    420                     if(flag){
    421                         row_dict['id'] = nid;
    422                     }
    423                     all_list.push(row_dict)
    424 
    425 
    426                 });
    427 
    428                 // 通过Ajax提交后台
    429                 $.ajax({
    430                     url: url,
    431                     type: 'PUT',
    432                     data: JSON.stringify(all_list),
    433                     success:function(arg){
    434                         console.log(arg);
    435                     }
    436                 })
    437             });
    438 
    439             $('.search-list').on('click','li',function(){
    440                 // 点击li执行函数
    441                 var wenben = $(this).text();
    442                 var searchType = $(this).attr('search_type');
    443                 var name = $(this).attr('name');
    444                 var globalName = $(this).attr('global_name');
    445 
    446                 // 把显示替换
    447                 $(this).parent().prev().find('.searchDefault').text(wenben);
    448 
    449 
    450                 if(searchType == 'select'){
    451                     /*
    452                         [
    453                             [1,‘文本’],
    454                             [1,‘文本’],
    455                             [1,‘文本’],
    456                         ]
    457                      */
    458                     var sel = document.createElement('select');
    459                     $(sel).attr('class','form-control');
    460                     $(sel).attr('name',name);
    461                     $.each(GLOBAL_DICT[globalName],function(k,v){
    462                         var op = document.createElement('option');
    463                         $(op).text(v[1]);
    464                         $(op).val(v[0]);
    465                         $(sel).append(op);
    466                     });
    467                     $(this).parent().parent().next().remove();
    468                     $(this).parent().parent().after(sel);
    469                 }else{
    470                     var inp = document.createElement('input');
    471                     $(inp).attr('class','form-control');
    472                     $(inp).attr('name',name);
    473                       $(inp).attr('type','text');
    474                     $(this).parent().parent().next().remove();
    475                     $(this).parent().parent().after(inp);
    476                 }
    477 
    478             });
    479 
    480             $('.search-list').on('click','.add-search-condition',function(){
    481                 // 拷贝的新一搜索项
    482                 var newSearchItem = $(this).parent().parent().clone();
    483                 $(newSearchItem).find('.add-search-condition span').removeClass('glyphicon-plus').addClass('glyphicon-minus');
    484                 $(newSearchItem).find('.add-search-condition').addClass('del-search-condition').removeClass('add-search-condition');
    485                 $('.search-list').append(newSearchItem);
    486 
    487             });
    488 
    489              $('.search-list').on('click','.del-search-condition',function(){
    490                  $(this).parent().parent().remove();
    491              });
    492 
    493             $('#doSearch').click(function(){
    494                 initial(url);
    495             })
    496         }
    497     })
    498 })(jQuery);
    js 代码
     1 table_config=[
     2             {'q': None, 'title': '选择',
     3              'display': True,
     4              'text': {
     5                  'tpl': "<input type='checkbox' value='{n1}' />",
     6                  'kwargs': {'n1': '@id'}},
     7              },
     8             {'q':'id','title':'ID',
     9              'display':False,
    10              'text':{
    11                  'tpl':"{n1}",
    12                  'kwargs':{'n1':'@id'}
    13              }},
    14             {'q': 'hostname', 'title': '主机名',
    15              'display': True,
    16              'text': {
    17                  'tpl': "{n1}",
    18                  'kwargs': {'n1': '@hostname'}
    19              },
    20              'attrs':{'origin':'@hostname','name':'hostname','edit-enable':'true'}},
    21             {'q': 'create_at', 'title': '创建时间',
    22              'display': True,
    23              'text': {
    24                  'tpl': "{n1}",
    25                  'kwargs': {'n1': '@create_at'}
    26              }},
    27             {'q':None, 'title': '操作',
    28              'display': True,
    29              'text': {
    30                  'tpl': "<a href='/del?nid={nid}'>删除</a>",
    31                  'kwargs': {'nid': '@id'}
    32              }},
    33         ]
    34 table_config1=[
    35             {'q': None, 'title': '选择',
    36              'display':True,
    37              'text': {
    38                  'tpl': "<input type='checkbox' value='{n1}' />",
    39                  'kwargs': {'n1': '@id'}},
    40              'attrs':{'nid':'@id'}
    41              },
    42             {'q':'id','title':'ID',
    43              'display':False,
    44              'text':{
    45                  'tpl':"{n1}",
    46                  'kwargs':{'n1':'@id'}
    47              }
    48              },
    49             {'q': 'business_unit_id__name', 'title': '业务线',
    50              'display': True,
    51              'text': {
    52                  'tpl': "{n1}",
    53                  'kwargs': {'n1': '@business_unit_id__name'}
    54              },
    55              'attrs':{'k1':'v1','k2':'@id'}
    56              },
    57             {'q': 'device_type_id', 'title': '资产类型',
    58              'display': True,
    59              'text': {
    60                  'tpl': "{n1}",
    61                  'kwargs': {'n1': '@@device_type_choices'}
    62              },
    63              'attrs': {'k1': 'v1', 'nid': '@id','origin':'@device_type_id','name':'device_type_id',
    64                             'edit-enable':'true','edit-type':'select','global_key':'device_type_choices'}},
    65             {'q': 'device_status_id', 'title': '状态',
    66              'display': True,
    67              'text': {
    68                  'tpl': "{n1}",
    69                  'kwargs': {'n1': '@@device_status_choices'}
    70              },
    71              'attrs': {'k1': 'v1', 'nid': '@id','origin':'@device_status_id','name':'device_status_id',
    72                             'edit-enable':'true','edit-type':'select','global_key':'device_status_choices'}},
    73             {'q':None, 'title': '操作',
    74              'display': True,
    75              'text': {
    76                  'tpl': "<a href='/del?nid={nid}'>删除</a>",
    77                  'kwargs': {'nid': '@id'}
    78              },'attrs':{'k1':'v1','k2':'@id'}
    79              },
    80         ]
    81 search_config =  [
    82     {'name': 'cabinet_num', 'text': '机柜号', 'search_type': 'input'},
    83     {'name': 'device_type_id', 'text': '资产类型', 'search_type': 'select', 'global_name': 'device_type_choices'},
    84     {'name': 'device_status_id', 'text': '资产状态', 'search_type': 'select', 'global_name': 'device_status_choices'},
    85 ]
    配置文件示例

     参考:http://www.cnblogs.com/nulige/p/6703160.html

  • 相关阅读:
    函数式编程理解
    Java8 lambda表达式10个示例
    MD5进行文件完整性校验的操作方法
    加密算法和MD5等散列算法的区别(转)
    随笔
    瑕疵(bug)严重性定义
    无需Cygwin,如果没有在命令行,Eclipse编NDK
    BZOJ 1878 SDOI 2009 HH项链 树状数组 + 脱机处理
    Teamcity+SVN+VisualStudio在持续集成简明教程
    UVALive3713-Astronauts(2-SAT)
  • 原文地址:https://www.cnblogs.com/liuguniang/p/7255619.html
Copyright © 2020-2023  润新知