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;width: 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="width: 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

 

posted @ 2017-07-29 13:14  柳姑娘  阅读(653)  评论(0)    收藏  举报