CMDB那些事儿 ------> 项目整理

一、CMDB项目所有笔记及总结

CMDB 项目笔记
 
1、为什么开发CMDB?
         
    背景:社会调查发现,很多公司都是用Excel表以管理维护公司内的所有资产信息,当设备发生改变或是修改配置,就需要相应的
管理人员去实时的更新这个表,手动的添加上相关的详细变更信息;
    但是这其中有个难题:资产变更时难以保证Excel表更正的正确性和实时性;这进而又引发出一个问题,就是信息交换的不便利性
和准确性,原因就在于更新的不及时和信息记录的随意
         
    目标:为了解决上述考察的一系列问题,就需要开发一套自动化采集资产的工具,实现:资产自动采集更新,数据自动汇报,实时保存变更记录的功能。
         
    最终目标:实现运维自动化
     
2、CMDB架构?
    大致分为三个部分:
        - 资产采集(资产采集)
        - API(一是接受采集的数据保存入库;二是对外提供数据访问接口,供用户查询)
        - 后台管理  可视化的操作界面
         
运维愿景:
    1. 自动装机
    2. 配置管理
    3. 监控
    4. 堡垒机
 
    必备:资产管理
     
     
目前状况:
    手动维护Excel表格
    资产自动采集并汇报入库
     
    CMDB - 配置管理数据库(资产管理)
         
如何实现自动采集?(4种方法)
    利用subprocess模块和Linux基本命令,基于公司现使用机器批量管理程序的基础上,实现资产采集!
    v = subprocess.getoutput('ls')
    1. Agent
        程序直接在服务器端执行
    2. SSH类,paramiko,
        pip3 install paramiko  
    3. saltstack(Python开发)
        master
            yum install salt-master
             
            配置:1.1.1.1
            service salt-master start
             
        salve
            yum install salt-minion
            配置:
                master: 1.1.1.1
            service salt-master start
        salve
            yum install salt-minion
            配置:
                master: 1.1.1.1
            service salt-master start      
             
        授权:
            在母机上操作
            salt-key -L  查看所有授权的子机
            salt-key -A  添加所有子机
        执行命令:
            在master上执行: salt "*" cmd.run "ifconfig"
 
    4. puppet(ruby)
             
 
    资产管理获取数据方式(4种):到最后都是正则匹配获取有效信息,存于数据库
         
        1、agent (机器多时适用,但对服务器的性能有损耗)
            每台服务器上都有个获取当前服务器信息的脚本,每台机器定时自动执行并向接收端汇报信息。
             
            获取信息方式:使用subprocess模块
                import subprocess
                import re
                v = subprocess.getoutput("操作方法") 获取查询返回的字符串信息。然后利用正则的方法,去提取我们需要的字段信息
                s = re.match("正则规则",v)
             
            将结果发送给接收端 API (django框架接收)
                发送需要注意三点:1、url,2、发送数据的格式,3、发送数据完成之后的返回值
                import request  导入模块
                    v = request.post(url,data={"k1":v1,"k2":v2}) 以哪种方式发送数据,需要写入url地址及要发送的data数据,发送成功之后有返回值。
                 
             
        2、Paramiko ssh(机器少时适用,不影响服务器的性能,但是由于是基于网络发起的通信,耗时长)
             
            在接收端和服务器端之间再有一台服务器作为中控机,用这台中控机向要管理的所有服务器进行远程管理,发送ssh命令获取信息。再把

获取到的所有信息发送给接收端。
            需要下载模块 Paramiko 利用这个模块进行ssh远程通信。
             
        3、SaltStack方式
             
            他是通过master母机,管理所有的子机minion实现批量管理的功能。当母机发送一条命令时,所有的子机都会执行这条命令并把处理的结果
返回给母机。 要想实现管理,就需要给母机装上master协议,所有的子机装上minion协议,母机配置interface IP地址,子机配置母机master 的IP
地址,在分别重启服务。然后母机授权 (salt-key -L 查看已授权的子机,salt-key -A 为所有子机授权),否则无法实现管理。 母机测试代码(*代表所有):salt "*" cmd.run "ls" 查看本地能否得到返回信息。 这种模型,是以中控机作为母机,要管理的所有服务器作为子机。 母机执行py文件发送命令并接收返回值: v = subprocess.getoutput(salt "*" cmd.run "ls") SaltStack执行原理: 母机与子机之间通信,中间存在一个消息队列,母机把操作发送到消息队列中,所有的子机去消息队列获取
这个操作然后执行对应的方法。执行结束之后, 所有的子机 会把处理完的结果全部放到一个临时存在的消息队列中,然后母机从这个临时队列中把所有的
子机返回的信息取出来。这样就算是完成了一次通信。 应用场景:机器比较多,公司已经在使用saltstack 4、puppet(ruby开发) 也是有一个中控机与所有的服务器通信,但是这种关联机制是又ruby开发的。服务器与中控机之间默认是每30分钟
通信一次,基于这个条件在遵循ruby规则写一个文件,去获取 每台机器的资产信息。但是还需要去学ruby。 应用场景:老牌公司使用puppet 目标: 兼容三种采集方式软件 高内聚:所有的功能都集合到一起自己文件内全部完成 低耦合:各模块之间无绝对关系,都是相对独立 导入模块的方法:【以字符串的形式导入模块】 传统模式:import 模块名 以字符串的形式导入模块 import importlib m = importlib.import_module('字符串类型') 常看当前文件的所有方法(名称空间):dir() os.environ()获取系统信息,字典类型(键值全是字符串),会列出当前所有的环境变量 加值的话,仅在当前py文件下的环境变量生效,运行结束之后就会消失,不会影响其他文件。 traceback 导入堆栈模块,用于程序出错时生成错误的详细信息 traceback.format_exc() ---> 获取详细的错误信息 配置文件: 配置信息名字一般都是大写 两种类型: 1、只支持用户定义: 2、支持用户定义的文件 和 默认内置的配置文件 就需要把这两类文件整合到一起,通过另一个文件创建一个类,导入这之下所有的方法,引用的时候直接引用这个类的对象即可。 客户端程序设计目录: bin - 用于存放可执行文件 - start.py 开始程序 conf - 用于存放自定义文件 - settings.py 自定义的配置信息 lib - 库文件,用于存放一些内定的配置信息 - conf - config.py 配置整合文件,通过该文件内的类,将自定义和内置配置全部获取到供全局使用 - global_settings.py 全局设置的文件(内置) - convert.py 用于数据转换 src - 用于存放处理业务的文件 - plugins 用于存放处理各个模块的文件 - client.py 用于定义通过不同方式获取资产信息的方法 - script.py 定义执行方法的函数,通过配置文件判定获取资产方式。 设计注意点: 1、关于日志文件,一般是选取硬盘上的某个文件存放日志,而不是在程序中设计一个文件夹存放! 原因:日志会越来越多,每天都在生成。还不如找一个专门的位置存放日志妥当;代码要有精简性和可移植性,如果把日志
目录放在代码程序中,等后期日志多了, 要拷贝该程序到别的服务器上,岂不是说还要把庞大的日志信息也考走? 总结: 代码库一定要和这种未来要大量写入信息的文件 解耦分离!便于以后维护,移动和管理!!! 整个开发流程: 1. 目录 bin config lib src 2. 配置文件 只支持用户定义: settings.py USER = 'root' PWD = "sdfsdf" 支持用户定义 默认配置文件: settings.py USER = 'root' PWD = "sdfsdf" from lib.conf.config import settings PS: import os os.environ['USER_SETTINGS'] = "config.settings" 3. 开发插件(可插拔) 公司采集资产有差别 默认采集: basic board cpu disk memory nic 定义插件: class xxxxx(object): def process(self): return '1123123123' 写配置文件: PLUGINS_DICT = { ... 'xxx': "xxx.xxx.xxx.xxxx", ... } 执行命令: - MegaCli - dmidecode 4. 向API获取发送数据 host采集资产,发送到API AGENT: 向API发送资产信息 SSH、SALT: 获取未采集的主机列表:[c1.com,c2.com] for host in 主机列表: host采集资产,发送到API class Base(obj): def post_asset(self,server_info): 向API发送资产信息 class Agent(Base): def execute(self): server_info = PluginManager().exec_plugin() self.post_asset(server_info) class SSHSALT(Base): def get_host(self): # 获取未采集的主机列表: return [c1.com,c2.com] def execute(self): host_list = self.get_host() for host in host_list: server_info = PluginManager(host).exec_plugin() self.post_asset(server_info) 5、后台管理 全插件 开发过程中遇到的难题(深坑) Linux:Linux的操作命令不熟 API采集数据 1、唯一标识:标准化 机器型号,系统,软件,环境,主机名,放置机柜位置及该机器的标志位名字, 原因:采集资产汇报信息入库的时候,以哪个信息作为标识就是一个大问题 如果公司全是物理机,那么利用主板的SN码完全没问题, 但是如果是一台物理机上有多个虚拟机呢?那所有虚拟机返回的SN号就会完全一致,这样就导致信息的错误。 此时就需要人为的标准化信息:主机名不重复;通过主机名去做唯一标识,通过主机名去做判断。 标准化: - 主机名不重复。 - 流程: - 手动资产录入:机房,机柜,机柜位置, - 装机时,需要将服务器的基本信息录入CMDB,此时已定主机名 - 资产采集: 主机名是唯一标识,依赖本地文件 步骤: a. 装系统,初始化软件(CMDB),运行CMDB - 通过命令获取主机名 - 写入本地指定文件 b. 将资产信息发送到API c. 获取资产信息: 判定本地文件主机名是否与命令获取的主机名一致? - 1、一致的话就更新 - 2、不一致按照本地文件写入 ======================================================================================= 最终流程: 唯一标识 标准化:主机名不重复;流程标准化(装机同时,主机名也需要在cmdb中手动输入设置) 服务器资产采集(Agent): a. 第一次:文件不存在,或内容为空; 采集资产: - 从basic信息中获取主机名,然后写入文件 - 发送API b. 第N次:采集资产,主机名:文件中获取 注意:规避了一点,主机名是从文件中获取的,如果主机名临时做了更改,对此台机器做资产采集 不会受影响;如果是需要对本机做正式的修改则需要去文件变更。 代码: server_info = PluginManager().exec_plugin() hostname = server_info['basic']['data']['hostname'] certname = open(settings.CERT_PATH,'r',encoding='utf-8').read().strip() if not certname: with open(settings.CERT_PATH,'w',encoding='utf-8') as f: f.write(hostname) else: server_info['basic']['data']['hostname'] = certname SSH或Salt: 中控机:先从API中获取未采集主机名列表:【c1.com 】 SSH和Salt不存在这种情况:这个架构执行初始是从中控机获取没有采集过的主机名(主机名跟IP是绑定的),而流程一开始主机名已经被录入cmdb了 中控机中高并发问题 2、线程池: 为提交采集信息的效率而实现高并发 目的:解决SSH或salt模式下,由原始的串行获取数据的方式,转变成多条任务同时执行获取数据;最终目标:节省时间,快速高效的完成任务 方式:结合线程和进程的知识,开启进/线程池,用以提高并发执行操作:线程、进程 线程池或进程池:20 Python2: 线程池:无 进程池:有 Python3: 线程池:有 进程池:有 代码举例: import time from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor (导入进程或是线程池模块) def task(i): #要执行的函数 time.sleep(1) print(i) p = ThreadPoolExecutor(10) #创建线程池对象,数字代表开启多少个线程 for row in range(100): p.submit(task,row) #利用这个对象的 submit方法 触发多个任务,参数:第一个为要执行的函数,第二个为要执行函数传入的参数! 3、API验证 1、为什么要做API验证? - 保证仅是有权限的人才能访问 - 访问过程中,保证数据安全,库不会被篡改 2、你是如何设计的? - 与Tornado 中加密Cookie类似 - 创建动态KEY:md5(key+time)|time - 为防止被黑客截获KEY,利用某人API,操作我后台数据,优化代码加以限制! - 此处要:维护一个访问记录表(字典类型),仅当前10秒内访问的所有用户,超时相应用户会删除! (解释:为什么要10s,各位看官,如果是每次访问生成的动态KEY我都记录的话,那到后期我这个表得大到什么地步!!!创建他的目的是优化访问权限,而不是增加累赘) - 实现优化的鬼门三关(仅为娱乐) 第一关:时间 ------------> 超时无权限 第二关:算法规则 --------> 数据截获篡改,MD5算法值不一致,滚粗! 第三关:已访问记录 ------> 俗称人头关,首次访问会记录,同样的KEY再来一次,你当我算法是菜啊! 代码: - 如果黑客截获KEY,他的网速比你快无比,这样是不是数据又被截获泄漏了?!怎么解决?--->数据加密! 这样他来访问,就是来拉高你的网速的!!! 3、数据加密方法: - RSA(公钥私钥) ----> 由于内部有字节限制操作,生成公钥时需要指定bytes位数,同时也不清楚每次发送数据的大小,拘谨很大! 摒弃吧!我的哥,我们需要的是随心所遇的加密方法! - AES(仿微信高级加密) ---> 通过一个16位字节格式(及倍数)的key,对数据进行加密(数据必须是要bytes)一定要注意,此种加密的数据长度必须是16个字节的倍数! 不够则需要在字节数据之后加值凑够倍数,解码的时候,再把凑数的值去掉,获取真正的数据! 代码: 参考武sir:http://www.cnblogs.com/wupeiqi/articles/6746744.html 数据存储问题 4、数据库设计 此处牵扯到项目三层(五层)架构设计的问题,及各个操作各司其职,互不干扰! (高内聚,低耦合的原则,各个模块分离,各司其职,使用的话直接调用) 数据库访问层 - User:增,删除,修改 - ... 处理所有的数据库操作方法或业务! 业务处理层 - 用户: - 授权: - 。。。 处理所有的业务逻辑, UI层 仅为展示数据 - html - UI展示层 可视化操作,用于编写展示所有数据的方法 总结:业务分离解耦,业务变更直接修改所在模块的内部方法。其他逻辑处理层需要使用该模块下的方法,直接导入应用。 - 数据库表结构设计: 详见CMDB数据库表结构设计图 5、后台管理 - 序列化: 1. Django内置 # from django.core import serializers # # 序列化queryset:[obj,obj,obj,obj] # v = models.Server.objects.all() # data = serializers.serialize("json", v) 2. JSON + 扩展 # json扩展:支持时间序列化 from datetime import datetime from datetime import date class JsonCustomEncoder(json.JSONEncoder): def default(self, value): if isinstance(value, datetime): return value.strftime('%Y-%m-%d %H:%M:%S') #格式化的时间字符串 elif isinstance(value, date): return value.strftime('%Y-%m-%d') #格式化的时间字符串 else: return json.JSONEncoder.default(self, value) v = models.Server.objects.values('id','hostname','create_at') data = json.dumps(list(v),cls=JsonCustomEncoder) RESTful API 面向资源编程:网络上任何东西当作资源 接口定义规则:一个url 对应一个视图函数,函数内部利用request.method的不同请求方式,做增删改查操作 request.method == "GET" 获取数据 request.method == "POST" 增加数据 request.method == "PUT" 更新数据 request.method == "DELETE" 删除数据 项目回顾: 1. 为什么开发CMDB? Excel维护资产信息,资产变更时难以保证Excel表正确性;信息交换不方便 自动采集资产工具,目标:自动汇报,保存变更记录。 最终目标:实现运维自动化 2. CMDB架构? - 资产采集(资产采集) - API(接受数据保存入库,对外提供数据接口) - 后台管理 3. 你负责做什么? - 资产采集(资产采集) 三种方案: - agent - paramiko - saltstack 提高扩展性,客户端配置是参考Django:配置,中间件(反射)设计的! 难题:错误堆栈信息 4. 有没有遇到难题(坑)? Linux:Linux不太熟 唯一标识:大问题 ????????????????

  

二、各种图(意思理解了即好!):

1、client端资产采集原理草图:

2、整个项目设计草图:

3、服务端数据数据库表结构设计图:

进阶:

 
 
 
 
 
 

 

 

CMDB

 

CMDB简介

公司:
   开发
   测试:功能测试  性能测试(高并发)
   运维:上线  copy--解压--运行  python manage.py run serve 
   DBA

提供机房服务的公司:兆维,世纪互联

运维人员工作:   
   装机------自动装机系统
   配管系统(配置管理)(远程登录机器装软件,初始化操作)   代码部署
   监控系统
   堡垒机-----操作日志
   上线



服务器资产管理问题:
    之前使用Excel表格维护资产,依赖人为性操作,资产变更时,容易出错,且效率低。与其他部门进行信息交换时,没有

一个固定的数据形式。

目的:自动采集和汇报,保存变更记录

实现运维自动化
高内聚,低耦合

CMDB 资产管理数据库架构:
资产采集
API(接收数据,对外提供信息接口)
后台管理(数据显示)

  

资产采集的实现方案
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速度快
原理:

SaltStack 采用`C/S`模式,server端就是salt的master,client端就是minion,minion与master之间通过`ZeroMQ`消息队列通信。

minion上线后先与master端联系,把自己的`pub key`发过去,这时master端通过`salt-key -L`命令就会看到minion的key,接受该minion-key后,也就是master与minion已经互信。

master可以发送任何指令让minion执行了,salt有很多可执行模块,比如说cmd模块,在安装minion的时候已经自带了,它们通常位于你的python库中,`locate salt | grep /usr/`可以看到salt自带的所有东西。

这些模块是python写成的文件,里面会有好多函数,如cmd.run,当我们执行`salt '*' cmd.run 'uptime'`的时候,master下发任务匹配到的minion上去,minion执行模块函数,并返回结果。

master监听4505和4506端口,4505对应的是ZMQ的PUB system,用来发送消息,4506对应的是REP system是来接受消息的。
具体步骤如下

```
1、Salt stack的Master与Minion之间通过ZeroMq进行消息传递,使用了ZeroMq的发布-订阅模式,连接方式包括tcp,ipc
2、salt命令,将cmd.run ls命令从salt.client.LocalClient.cmd_cli发布到master,获取一个Jodid,根据jobid获取命令执行结果。
3、master接收到命令后,将要执行的命令发送给客户端minion。
4、minion从消息总线上接收到要处理的命令,交给minion._handle_aes处理
5、minion._handle_aes发起一个本地线程调用cmdmod执行ls命令。线程执行完ls后,调用minion._return_pub方法,将执行结果通过消息总线返回给master
6、master接收到客户端返回的结果,调用master._handle_aes方法,将结果写的文件中
7、salt.client.LocalClient.cmd_cli通过轮询获取Job执行结果,将结果输出到终端。
```

#### saltstack 安装

[saltstack install](http://repo.saltstack.com/#rhel)

#### 修改minion配置文件
```
[root@linux-node2 ~]# vim /etc/salt/minion
master: 192.168.56.11
[root@linux-node2 ~]# vim /etc/salt/minion
master: 192.168.56.11
[root@linux-node1 pki]# pwd
/etc/salt/pki
[root@linux-node1 pki]# tree
.
├── master
│   ├── master.pem
│   ├── master.pub
│   ├── minions
│   ├── minions_autosign
│   ├── minions_denied
│   ├── minions_pre
│   │   ├── linux-node1.example.com
│   │   └── linux-node2.example.com
│   └── minions_rejected
└── minion
    ├── minion_master.pub
    ├── minion.pem
    └── minion.pub
[root@linux-node1 pki]# salt-key -A
[root@linux-node1 pki]# tree
.
├── master
│   ├── master.pem
│   ├── master.pub
│   ├── minions
│   │   ├── linux-node1.example.com
│   │   └── linux-node2.example.com
│   ├── minions_autosign
│   ├── minions_denied
│   ├── minions_pre
│   └── minions_rejected
└── minion
    ├── minion_master.pub
    ├── minion.pem
    └── minion.pub
```

#### 远程执行
```
[root@linux-node1 pki]# salt "*" test.ping
linux-node2.example.com:
    True
linux-node1.example.com:
    True
[root@linux-node1 pki]# salt "*" cmd.run 'w'
linux-node1.example.com:
     07:20:24 up 17:10,  1 user,  load average: 0.00, 0.01, 0.05
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     pts/0    192.168.56.1     07:04    0.00s  0.30s  0.26s /usr/bin/python /usr/bin/salt * cmd.run w
linux-node2.example.com:
     08:26:25 up 22:40,  2 users,  load average: 0.15, 0.05, 0.06
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    root     tty1                      Sat09   13:12m  0.02s  0.02s -bash
    root     pts/0    192.168.56.1     08:09   13:53   0.04s  0.04s -bash
```

#### 配置管理
##### YAML

- 缩进:
  - 两个空格
  - 不能使用tab键
  - 缩进代表层级关系

- 冒号:
  - key: value

- 短横线代表list

#### satate模块
```
# vim /etc/salt/master
file_roots:
  base:
    - /srv/salt
# mkdir /srv/salt
# mkdir /srv/salt
# cd /srv/salt
# mkdir web
# cd web
# pwd
/srv/salt/web
# vim apache.sls
apache-install:
  pkg.installed:
    - names:
      - httpd
      - httpd-devel

apache-service:
  service.running:
    - name: httpd
    - enable: True

# salt '*' state.sls web.apache
 [root@linux-node2 salt]# cd /var/cache/salt/
[root@linux-node2 salt]# tree               
.
`-- minion
    |-- extmods
    |-- files
    |   `-- base
    |       `-- web
    |           `-- apache.sls
    |-- pkg_refresh
    `-- proc
        `-- 20160605081351939477 
# cat /var/cache/salt/minion/files/base/web/apache.sls 
apache-install:
  pkg.installed:
    - names:
      - httpd
      - httpd-devel

apache-service:
  service.running:
    - name: httpd
    - enable: True   
# ps -ef|grep yum
root      34129  34103  1 08:13 ?        00:00:00 /usr/bin/python /usr/bin/yum --quiet check-update
root      34204  34149  0 08:14 pts/1    00:00:00 grep --color=auto yum

# cd /srv/salt/
# vim top.sls
base:
  'linux-node1.example.com':
    - web.apache
  'linux-node2.example.com':
    - web.apache
# salt '*' state.highstate test=True
# salt '*' state.highstate

# lsof -i:4505 -n
COMMAND     PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
salt-mast 24739 root   13u  IPv4 4637762      0t0  TCP *:4505 (LISTEN)
salt-mast 24739 root   15u  IPv4 4640421      0t0  TCP 192.168.56.11:4505->192.168.56.11:48344 (ESTABLISHED)
salt-mast 24739 root   16u  IPv4 4640542      0t0  TCP 192.168.56.11:4505->192.168.56.12:53039 (ESTABLISHED)
salt-mini 25378 root   25u  IPv4 4640888      0t0  TCP 192.168.56.11:48344->192.168.56.11:4505 (ESTABLISHED)
```

#### 数据系统

##### Grains
静态数据 当minion启动时收集的minion本地相关信息
View Code

 

 

4. puppet(ruby)每30分钟连接一次master,执行一次ruby脚本
场景:公司现在在使用puppet

 

 代码流程:

资产采集部分

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

     测试模式:debug
向API发送数据
  从API获取未采集资产
View Code

问题:

唯一标识:

周期:2-3个月,3个人
你负责做什么?
  3处借鉴了Django源码的设计模式:
默认配置和自定义配置
中间件---插件做成可插拔的模式,增加采集资源的插件时,只要写一个类(命令+结果格式化)
              在配置文件中写上路径,就可以采集资源的信息
             用到了反射
  遇到的难题:唯一标识
 唯一标识
      所有物理硬件上的标识不能作为唯一标识
           主板SN号:虚拟机的SN号可能相同
           IP地址会变
           Mac地址不准确
 标准化:
   --主机名不重复,作为唯一标识
   --流程标准化
            --资产录入,机房,机柜,机柜位置
            --装机时,需要将服务信息录入CMDB
            --资产采集   

 最终流程:标准化:主机名不重复。流程标准化:装机同时,主机名在cmdb中设置

 步骤:
   agent:
       a. 装系统,初始化软件cmdb,运行cmdb
               --通过命令获取主机名
               --写入本地指定文件
       b. 将资产信息发送到API
       c.获取资产信息
              -本地文件主机名!= 命令获取的主机名(按照文件中的主机名)
              -本地文件住居明==命令获取的文件主机名
   ssh/salt:
       中控机:获取未采集主机名列表
View Code

线程池:

2 线程池
  提高并发
  python2: 进程池
  python3:线程池 进程池
  代码示例:
     from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
     def task(i)
         print(i)

     p=ThreadPoolExecutor(10)
     for row in range(100)
          p.submit(task,row)
View Code

API验证:


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

#API验证
# 发令牌  静态, 隐患:易被他人截取

import requests
key='ncjfsvnjsflbvfjslgbvhglbhfbh'
response=requests.get('http://127.0.0.1:8000/api/asset/',headers={'openkey':key})
print(response.text)

# 改良 动态令牌   隐患:易被他人截取
import time
import requests
import hashlib
#
ctime=time.time()
key='vmkdsf;nvfglnbglbngjflbn'
new_key='%s|%s'%(key,ctime)

m=hashlib.md5()
m.update(bytes(new_key,encoding='utf8'))
md5_key=m.hexdigest()

md5_time_key='%s|%s'%(md5_key,ctime)
response=requests.get('http://127.0.0.1:8000/api/asset/',headers={'openkey':md5_time_key})
print(response.text)

#解决方法
#   ---记录已发送的md5_time_key
#   ---时间限制:将10s以外的排除
#   将两个限制结合起来

#隐患:黑客网速快
# 不管:搭建内网
# 管:数据加密
研究过程

最终代码

def post_asset(self,server_info):
        #数据加密
        server_info=json.dumps(server_info)
        server_info=self.xxxxxx(server_info)
        #API验证
        ctime=time.time()
        key='vmkdsf;nvfglnbglbngjflbn'
        new_key='%s|%s'%(key,ctime)

        m=hashlib.md5()
        m.update(bytes(new_key,encoding='utf8'))
        md5_key=m.hexdigest()

        md5_time_key='%s|%s'%(md5_key,ctime)

        response=requests.get(
            url=settings.API,
            headers={'openkey':md5_time_key,'Content-Type':'application/json'},
            data=server_info)
        # response = requests.get(settings.API,headers={'openkey': md5_time_key,},json=server_info)
        return response.text
客户端
def api_confirm(func):
    def wrapper(request):
            api_key_record = {}
            client_md5_time_key = request.META.get('HTTP_OPENKEY')
            client_md5_key, client_time = client_md5_time_key.split('|')
            client_time = float(client_time)
            server_time = time.time()

            # 第一关 排除超过10秒的请求
            if server_time - client_time > 10:
                return HttpResponse('你网络太慢了吧,重新发')
            # 第二关:匹配MD5值

            temp = "%s|%s" % (settings.AUTH_KEY, client_time,)
            m = hashlib.md5()
            m.update(bytes(temp, encoding='utf-8'))
            server_md5_key = m.hexdigest()
            if server_md5_key != client_md5_key:
                return HttpResponse('修改时间了吧,你还嫩点')

            # 将过期的MD5值记录删除
            for k in list(api_key_record.keys()):
                v = api_key_record[k]
                if server_time > v:
                    del api_key_record[k]

            # 第三关:检查此MD5值10秒之内是否访问过
            if client_md5_time_key in api_key_record:
                return HttpResponse('是你,是你,就是你,heck')
            else:
                api_key_record[client_md5_time_key] = client_time + 10
            return func(request)
    return wrapper
服务端(装饰器)

数据库设计 

 

from django.db import models


class UserProfile(models.Model):
    """
    用户信息
    """
    name = models.CharField(u'姓名', max_length=32)
    email = models.EmailField(u'邮箱')
    phone = models.CharField(u'座机', max_length=32)
    mobile = models.CharField(u'手机', max_length=32)

    class Meta:
        verbose_name_plural = "用户表"

    def __str__(self):
        return self.name


class AdminInfo(models.Model):
    """
    用户登陆相关信息
    """
    user_info = models.OneToOneField("UserProfile")
    username = models.CharField(u'用户名', max_length=64)
    password = models.CharField(u'密码', max_length=64)

    class Meta:
        verbose_name_plural = "管理员表"

    def __str__(self):
        return self.user_info.name


class UserGroup(models.Model):
    """
    用户组
    """
    name = models.CharField(max_length=32, unique=True)
    users = models.ManyToManyField('UserProfile')

    class Meta:
        verbose_name_plural = "用户组表"

    def __str__(self):
        return self.name


class BusinessUnit(models.Model):
    """
    业务线
    """
    name = models.CharField('业务线', max_length=64, unique=True)
    contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c')
    manager = models.ForeignKey('UserGroup', verbose_name='系统管理员', related_name='m')

    class Meta:
        verbose_name_plural = "业务线表"

    def __str__(self):
        return self.name


class IDC(models.Model):
    """
    机房信息
    """
    name = models.CharField('机房', max_length=32)
    floor = models.IntegerField('楼层', default=1)

    class Meta:
        verbose_name_plural = "机房表"

    def __str__(self):
        return self.name


class Tag(models.Model):
    """
    资产标签
    """
    name = models.CharField('标签', max_length=32, unique=True)

    class Meta:
        verbose_name_plural = "标签表"

    def __str__(self):
        return self.name


class Asset(models.Model):
    """
    资产信息表,所有资产公共信息(交换机,服务器,防火墙等)
    """
    device_type_choices = (
        (1, '服务器'),
        (2, '交换机'),
        (3, '防火墙'),
    )
    device_status_choices = (
        (1, '上架'),
        (2, '在线'),
        (3, '离线'),
        (4, '下架'),
    )

    device_type_id = models.IntegerField(choices=device_type_choices, default=1)
    device_status_id = models.IntegerField(choices=device_status_choices, default=1)

    cabinet_num = models.CharField('机柜号', max_length=30, null=True, blank=True)
    cabinet_order = models.CharField('机柜中序号', max_length=30, null=True, blank=True)

    idc = models.ForeignKey('IDC', verbose_name='IDC机房', null=True, blank=True)
    business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True)

    tag = models.ManyToManyField('Tag')

    latest_date = models.DateField(null=True)
    create_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "资产表"

    # def __str__(self):
    #     return "%s-%s-%s" % (self.idc.name, self.cabinet_num, self.cabinet_order)


class Server(models.Model):
    """
    服务器信息
    """
    asset = models.OneToOneField('Asset')

    hostname = models.CharField(max_length=128, unique=True)
    sn = models.CharField('SN号', max_length=64, db_index=True)
    manufacturer = models.CharField(verbose_name='制造商', max_length=64, null=True, blank=True)
    model = models.CharField('型号', max_length=64, null=True, blank=True)

    manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True)

    os_platform = models.CharField('系统', max_length=16, null=True, blank=True)
    os_version = models.CharField('系统版本', max_length=16, null=True, blank=True)

    cpu_count = models.IntegerField('CPU个数', null=True, blank=True)
    cpu_physical_count = models.IntegerField('CPU物理个数', null=True, blank=True)
    cpu_model = models.CharField('CPU型号', max_length=128, null=True, blank=True)

    create_at = models.DateTimeField(auto_now_add=True, blank=True)

    class Meta:
        verbose_name_plural = "服务器表"

    def __str__(self):
        return self.hostname


class NetworkDevice(models.Model):
    asset = models.OneToOneField('Asset')
    management_ip = models.CharField('管理IP', max_length=64, blank=True, null=True)
    vlan_ip = models.CharField('VlanIP', max_length=64, blank=True, null=True)
    intranet_ip = models.CharField('内网IP', max_length=128, blank=True, null=True)
    sn = models.CharField('SN号', max_length=64, unique=True)
    manufacture = models.CharField(verbose_name=u'制造商', max_length=128, null=True, blank=True)
    model = models.CharField('型号', max_length=128, null=True, blank=True)
    port_num = models.SmallIntegerField('端口个数', null=True, blank=True)
    device_detail = models.CharField('设置详细配置', max_length=255, null=True, blank=True)

    class Meta:
        verbose_name_plural = "网络设备"


class Disk(models.Model):
    """
    硬盘信息
    """
    slot = models.CharField('插槽位', max_length=8)
    model = models.CharField('磁盘型号', max_length=32)
    capacity = models.FloatField('磁盘容量GB')
    pd_type = models.CharField('磁盘类型', max_length=32)
    server_obj = models.ForeignKey('Server',related_name='disk')

    class Meta:
        verbose_name_plural = "硬盘表"

    def __str__(self):
        return self.slot


class NIC(models.Model):
    """
    网卡信息
    """
    name = models.CharField('网卡名称', max_length=128)
    hwaddr = models.CharField('网卡mac地址', max_length=64)
    netmask = models.CharField(max_length=64)
    ipaddrs = models.CharField('ip地址', max_length=256)
    up = models.BooleanField(default=False)
    server_obj = models.ForeignKey('Server',related_name='nic')


    class Meta:
        verbose_name_plural = "网卡表"

    def __str__(self):
        return self.name


class Memory(models.Model):
    """
    内存信息
    """
    slot = models.CharField('插槽位', max_length=32)
    manufacturer = models.CharField('制造商', max_length=32, null=True, blank=True)
    model = models.CharField('型号', max_length=64)
    capacity = models.FloatField('容量', null=True, blank=True)
    sn = models.CharField('内存SN号', max_length=64, null=True, blank=True)
    speed = models.CharField('速度', max_length=16, null=True, blank=True)

    server_obj = models.ForeignKey('Server',related_name='memory')


    class Meta:
        verbose_name_plural = "内存表"

    def __str__(self):
        return self.slot


class AssetRecord(models.Model):
    """
    资产变更记录,creator为空时,表示是资产汇报的数据。
    """
    asset_obj = models.ForeignKey('Asset', related_name='ar')
    content = models.TextField(null=True)# 新增硬盘
    creator = models.ForeignKey('UserProfile', null=True, blank=True)
    create_at = models.DateTimeField(auto_now_add=True)


    class Meta:
        verbose_name_plural = "资产记录表"

    def __str__(self):
        return "%s-%s-%s" % (self.asset_obj.idc.name, self.asset_obj.cabinet_num, self.asset_obj.cabinet_order)


class ErrorLog(models.Model):
    """
    错误日志,如:agent采集数据错误 或 运行错误
    """
    asset_obj = models.ForeignKey('Asset', null=True, blank=True)
    title = models.CharField(max_length=16)
    content = models.TextField()
    create_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name_plural = "错误日志表"

    def __str__(self):
        return self.title
View Code

数据展示层

<div>
        <div class="search-list clearfix" style="position: relative">

            <div class="search-btn col-md-offset-9 col-md-3" style="position: absolute;bottom: 1px;text-align: left" >
                <input id="doSearch" type="button" class="btn btn-primary" value="搜索" />
            </div>

            <div class="search-item col-md-offset-2 col-md-10 clearfix" style="position: relative;height: 35px;">
                <div style="position: absolute;left:0;width: 38px;">
                    <a type="button" class="btn btn-default add-search-condition">
                        <span class="glyphicon glyphicon-plus"></span>
                    </a>
                </div>
                <div class="input-group searchArea" style="position: absolute;left: 40px;right:300px;">
                    <div class="input-group-btn">
                        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                            <span class="searchDefault">默认值</span>
                            <span class="caret"></span>
                        </button>
                        <ul class="dropdown-menu">

                        </ul>
                    </div>
                    <!-- /btn-group -->
                    <!-- <input type="text" class="form-control" aria-label="..."> -->

                </div>
            </div>

        </div>
    </div>
组合搜索框
</head>
<body>
   <div style="width: 700px;margin: 0 auto">
       <div class="btn-group" role="group" aria-label="..." style="margin: 20px">
          <button id="checkAll" type="button" class="btn btn-default">全选</button>
          <button id="checkReverse" type="button" class="btn btn-default">反选</button>
          <button id="checkCancel" type="button" class="btn btn-default">取消</button>
          <button id="inOutEditMode" type="button" class="btn btn-default">进入编辑模式</button>
          <a class="btn btn-default" href="#">添加</a>
          <button id="multiDel" type="button" class="btn btn-default">删除</button>
          <button id="refresh" type="button" class="btn btn-default">刷新</button>
          <button id="save" type="button" class="btn btn-default">保存</button>
        </div>
        <table class="table table-bordered table-striped">
            <thead id="tbHead">
                <tr>

                </tr>
            </thead>
            <tbody id="tbBody">

            </tbody>
        </table>
    </div>
</body>
</html>
增删改查导航框
(function (jq) {
    var CREATE_SEARCH_CONDITION = true;
    var GLOBAL_DICT = {};
    /*
    {
        'device_type_choices': (
                                    (1, '服务器'),
                                    (2, '交换机'),
                                    (3, '防火墙'),
                                )
        'device_status_choices': (
                                    (1, '上架'),
                                    (2, '在线'),
                                    (3, '离线'),
                                    (4, '下架'),
                                )
    }
     */

    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
        return this.replace(/\{(\w+)\}/g, function (s, i) {
            return args[i];
        });
    };

    function getSearchCondition(){
        var condition = {};
        $('.search-list').find('input[type="text"],select').each(function(){

            /* 获取所有搜索条件 */
            var name = $(this).attr('name');
            var value = $(this).val();
            if(condition[name]){
                condition[name].push(value);
            }else{
                condition[name] = [value];
            }

        });
        return condition;
    }

    function initial(url) {
        // 执行一个函数, 获取当前搜索条件
        var searchCondition = getSearchCondition();
        console.log(searchCondition);
        $.ajax({
            url: url,
            type: 'GET',  // 获取数据
            data: {condition: JSON.stringify(searchCondition)},
            dataType: 'JSON',
            success: function (arg) {
                $.each(arg.global_dict,function(k,v){
                     GLOBAL_DICT[k] = v
                });
                initTableHeader(arg.table_config);
                initTableBody(arg.server_list, arg.table_config);
                initSearch(arg.search_config);
            }
        })
    }

    /*
    初始化搜索条件
     */
    function initSearch(searchConfig){
        if(searchConfig && CREATE_SEARCH_CONDITION){

            CREATE_SEARCH_CONDITION = false;
            // 找打searchArea ul,
            $.each(searchConfig,function(k,v){
                var li = document.createElement('li');
                $(li).attr('search_type', v.search_type);
                $(li).attr('name', v.name);
                if(v.search_type == 'select'){
                     $(li).attr('global_name', v.global_name);
                }

                var a = document.createElement('a');
                a.innerHTML = v.text;
                $(li).append(a);
                $('.searchArea ul').append(li);
            });

            // 初始化默认搜索条件
            // searchConfig[0],进行初始化
            // 初始化默认选中值
            $('.search-item .searchDefault').text(searchConfig[0].text);
            if(searchConfig[0].search_type == 'select'){
                var sel = document.createElement('select');
                $(sel).attr('class','form-control');
                $.each(GLOBAL_DICT[searchConfig[0].global_name],function(k,v){
                    var op = document.createElement('option');
                    $(op).text(v[1]);
                    $(op).val(v[0]);
                    $(sel).append(op)
                });
                $('.input-group').append(sel);
            }else{
                // <input type="text" class="form-control" aria-label="...">
                var inp = document.createElement('input');
                $(inp).attr('name',searchConfig[0].name);
                $(inp).attr('type','text');
                $(inp).attr('class','form-control');
                $('.input-group').append(inp);
            }


        }
    }

    function initTableHeader(tableConfig) {
        /*
         [
         {'q':'id','title':'ID'},
         {'q':'hostname','title':'主机名'},
         ]
         */
        $('#tbHead').empty();
        var tr = document.createElement('tr');
        $.each(tableConfig, function (k, v) {
            if (v.display) {
                var tag = document.createElement('th');
                tag.innerHTML = v.title;
                $(tr).append(tag);
            }
        });
        $('#tbHead').append(tr);
    }

    function initTableBody(serverList, tableConfig) {
        /*
         serverList = [
         {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
         {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
         {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
         {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
         ]
         */
        $('#tbBody').empty();
        $.each(serverList, function (k, row) {
            // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
            /*
             <tr>
             <td>id</td>
             <td>hostn</td>
             <td>create</td>
             </tr>
             */
            var tr = document.createElement('tr');
            tr.setAttribute('nid',row.id);
            $.each(tableConfig, function (kk, rrow) {
                // kk: 1  rrow:{'q':'id','title':'ID'},         // rrow.q = "id"
                // kk: .  rrow:{'q':'hostname','title':'主机名'},// rrow.q = "hostname"
                // kk: .  rrow:{'q':'create_at','title':'创建时间'}, // rrow.q = "create_at"
                if (rrow.display) {
                    var td = document.createElement('td');

                    /* 在td标签中添加内容 */
                    var newKwargs = {}; // {'n1':'1','n2':'123'}
                    $.each(rrow.text.kwargs, function (kkk, vvv) {
                        var av = vvv;
                        if(vvv.substring(0,2) == '@@'){
                            var global_dict_key = vvv.substring(2,vvv.length);
                            var nid = row[rrow.q];
                            $.each(GLOBAL_DICT[global_dict_key],function(gk,gv){
                                if(gv[0] == nid){
                                    av = gv[1];
                                }
                            })
                        }
                        else if (vvv[0] == '@') {
                            av = row[vvv.substring(1, vvv.length)];
                        }
                        newKwargs[kkk] = av;
                    });
                    var newText = rrow.text.tpl.format(newKwargs);
                    td.innerHTML = newText;

                    /* 在td标签中添加属性 */
                    $.each(rrow.attrs,function(atkey,atval){
                        // 如果@
                        if (atval[0] == '@') {
                            td.setAttribute(atkey, row[atval.substring(1, atval.length)]);
                        }else{
                            td.setAttribute(atkey,atval);
                        }
                    });

                    $(tr).append(td);
                }
            });
            $('#tbBody').append(tr);

        })
    }

    function trIntoEdit($tr){
        $tr.find('td[edit-enable="true"]').each(function(){
            // $(this) 每一个td
            var editType = $(this).attr('edit-type');
            if(editType == 'select'){
                // 生成下拉框:找到数据源
                var deviceTypeChoices = GLOBAL_DICT[$(this).attr('global_key')];

                // 生成select标签
                var selectTag = document.createElement('select');
                var origin = $(this).attr('origin');

                $.each(deviceTypeChoices,function(k,v){
                    var option = document.createElement('option');
                    $(option).text(v[1]);
                    $(option).val(v[0]);
                    if(v[0] == origin){
                        // 默认选中原来的值
                        $(option).prop('selected',true);
                    }
                    $(selectTag).append(option);
                });

                $(this).html(selectTag);
                // 显示默认值
            }else{
                // 获取原来td中的文本内容
                var v1 = $(this).text();
                // 创建input标签,并且内部设置值
                var inp = document.createElement('input');
                $(inp).val(v1);
                // 添加到td中
                $(this).html(inp);
            }


        });
    }
    function trOutEdit($tr){
        $tr.find('td[edit-enable="true"]').each(function(){
            // $(this) 每一个td
            var editType = $(this).attr('edit-type');
            if(editType == 'select'){
                var option = $(this).find('select')[0].selectedOptions;
                $(this).attr('new-origin',$(option).val());
                $(this).html($(option).text());
            }else{
                var inputVal = $(this).find('input').val();
                $(this).html(inputVal);
            }

        });
    }
    jq.extend({
        xx: function (url) {
            initial(url);

            // 所有checkbox绑定事件
            $('#tbBody').on('click',':checkbox',function(){
                // $(this) // checkbox标签
                // 1. 检测是否已经被选中
                if($('#inOutEditMode').hasClass('btn-warning')){
                    var $tr = $(this).parent().parent();
                    if($(this).prop('checked')){
                        // 进入编辑模式
                        trIntoEdit($tr);
                    }else{
                        // 退出编辑模式
                        trOutEdit($tr);
                    }
                }
            });

            // 所有按钮绑定事件
            $('#checkAll').click(function(){
                if($('#inOutEditMode').hasClass('btn-warning')){
                    $('#tbBody').find(':checkbox').each(function(){
                        if(!$(this).prop('checked')){
                            var $tr = $(this).parent().parent();
                            trIntoEdit($tr);
                            $(this).prop('checked',true);
                        }
                    })
                }else{
                    $('#tbBody').find(':checkbox').prop('checked',true);
                }

            });

            $('#checkReverse').click(function(){
                if($('#inOutEditMode').hasClass('btn-warning')){
                    $('#tbBody').find(':checkbox').each(function(){
                        var $tr = $(this).parent().parent();
                        if($(this).prop('checked')){
                            trOutEdit($tr);
                            $(this).prop('checked',false);
                        }else{
                            trIntoEdit($tr);
                            $(this).prop('checked',true);
                        }
                    })
                }else{
                    $('#tbBody').find(':checkbox').each(function(){
                        var $tr = $(this).parent().parent();
                        if($(this).prop('checked')){
                            $(this).prop('checked',false);
                        }else{
                            $(this).prop('checked',true);
                        }
                    })
                }
            });

            $('#checkCancel').click(function(){
                if($('#inOutEditMode').hasClass('btn-warning')){
                    $('#tbBody').find(':checkbox').each(function(){
                        if($(this).prop('checked')){
                            var $tr = $(this).parent().parent();
                            trOutEdit($tr);
                            $(this).prop('checked',false);
                        }
                    })
                }else{
                    $('#tbBody').find(':checkbox').prop('checked',false);
                }
            });

            $('#inOutEditMode').click(function(){
                if($(this).hasClass('btn-warning')){
                    // 退出编辑模式
                    $(this).removeClass('btn-warning');
                    $(this).text('进入编辑模式');
                    $('#tbBody').find(':checkbox').each(function(){
                        if($(this).prop('checked')){
                            var $tr = $(this).parent().parent();
                            trOutEdit($tr);
                        }
                    })
                }else{
                    // 进入编辑模式
                    $(this).addClass('btn-warning');
                    $(this).text('退出编辑模式');

                    $('#tbBody').find(':checkbox').each(function(){
                        if($(this).prop('checked')){
                            var $tr = $(this).parent().parent();
                            trIntoEdit($tr);
                        }
                    })
                }
            });

            $('#multiDel').click(function(){
                // $('#tbBody').find(':checkbox')
                var idList = [];
                $('#tbBody').find(':checked').each(function(){
                    var v = $(this).val();
                    idList.push(v)
                });

                $.ajax({
                    url: url,
                    type: 'delete',
                    data: JSON.stringify(idList),
                    success:function(arg){
                        console.log(arg);
                    }
                })

            });

            $('#refresh').click(function(){
                initial(url)
            });

            $('#save').click(function(){
                if($('#inOutEditMode').hasClass('btn-warning')){

                     $('#tbBody').find(':checkbox').each(function(){
                        if($(this).prop('checked')){
                            var $tr = $(this).parent().parent();
                            trOutEdit($tr);
                        }
                    })

                }

                var all_list = [];
                // 获取用户修改过的数据
                $('#tbBody').children().each(function(){
                    // $(this) = tr
                    var $tr= $(this);
                    var nid= $tr.attr('nid');
                    var row_dict = {};
                    var flag = false;
                    $tr.children().each(function(){
                        if($(this).attr('edit-enable')) {
                            if($(this).attr('edit-type') == 'select'){
                                var newData = $(this).attr('new-origin');
                                var oldData = $(this).attr('origin');
                                if(newData){
                                    if (newData != oldData) {
                                        var name = $(this).attr('name');
                                        row_dict[name] = newData;
                                        flag = true;
                                    }
                                }

                            }else{
                                var newData = $(this).text();
                                var oldData = $(this).attr('origin');
                                if (newData != oldData) {
                                    var name = $(this).attr('name');
                                    row_dict[name] = newData;
                                    flag = true;
                                }
                            }

                        }

                    });
                    if(flag){
                        row_dict['id'] = nid;
                    }
                    all_list.push(row_dict)


                });

                // 通过Ajax提交后台
                $.ajax({
                    url: url,
                    type: 'PUT',
                    data: JSON.stringify(all_list),
                    success:function(arg){
                        console.log(arg);
                    }
                })
            });

            $('.search-list').on('click','li',function(){
                // 点击li执行函数
                var wenben = $(this).text();
                var searchType = $(this).attr('search_type');
                var name = $(this).attr('name');
                var globalName = $(this).attr('global_name');

                // 把显示替换
                $(this).parent().prev().find('.searchDefault').text(wenben);


                if(searchType == 'select'){
                    /*
                        [
                            [1,‘文本’],
                            [1,‘文本’],
                            [1,‘文本’],
                        ]
                     */
                    var sel = document.createElement('select');
                    $(sel).attr('class','form-control');
                    $(sel).attr('name',name);
                    $.each(GLOBAL_DICT[globalName],function(k,v){
                        var op = document.createElement('option');
                        $(op).text(v[1]);
                        $(op).val(v[0]);
                        $(sel).append(op);
                    });
                    $(this).parent().parent().next().remove();
                    $(this).parent().parent().after(sel);
                }else{
                    var inp = document.createElement('input');
                    $(inp).attr('class','form-control');
                    $(inp).attr('name',name);
                      $(inp).attr('type','text');
                    $(this).parent().parent().next().remove();
                    $(this).parent().parent().after(inp);
                }

            });

            $('.search-list').on('click','.add-search-condition',function(){
                // 拷贝的新一搜索项
                var newSearchItem = $(this).parent().parent().clone();
                $(newSearchItem).find('.add-search-condition span').removeClass('glyphicon-plus').addClass('glyphicon-minus');
                $(newSearchItem).find('.add-search-condition').addClass('del-search-condition').removeClass('add-search-condition');
                $('.search-list').append(newSearchItem);

            });

             $('.search-list').on('click','.del-search-condition',function(){
                 $(this).parent().parent().remove();
             });

            $('#doSearch').click(function(){
                initial(url);
            })
        }
    })
})(jQuery);
js 代码
table_config=[
            {'q': None, 'title': '选择',
             'display': True,
             'text': {
                 'tpl': "<input type='checkbox' value='{n1}' />",
                 'kwargs': {'n1': '@id'}},
             },
            {'q':'id','title':'ID',
             'display':False,
             'text':{
                 'tpl':"{n1}",
                 'kwargs':{'n1':'@id'}
             }},
            {'q': 'hostname', 'title': '主机名',
             'display': True,
             'text': {
                 'tpl': "{n1}",
                 'kwargs': {'n1': '@hostname'}
             },
             'attrs':{'origin':'@hostname','name':'hostname','edit-enable':'true'}},
            {'q': 'create_at', 'title': '创建时间',
             'display': True,
             'text': {
                 'tpl': "{n1}",
                 'kwargs': {'n1': '@create_at'}
             }},
            {'q':None, 'title': '操作',
             'display': True,
             'text': {
                 'tpl': "<a href='/del?nid={nid}'>删除</a>",
                 'kwargs': {'nid': '@id'}
             }},
        ]
table_config1=[
            {'q': None, 'title': '选择',
             'display':True,
             'text': {
                 'tpl': "<input type='checkbox' value='{n1}' />",
                 'kwargs': {'n1': '@id'}},
             'attrs':{'nid':'@id'}
             },
            {'q':'id','title':'ID',
             'display':False,
             'text':{
                 'tpl':"{n1}",
                 'kwargs':{'n1':'@id'}
             }
             },
            {'q': 'business_unit_id__name', 'title': '业务线',
             'display': True,
             'text': {
                 'tpl': "{n1}",
                 'kwargs': {'n1': '@business_unit_id__name'}
             },
             'attrs':{'k1':'v1','k2':'@id'}
             },
            {'q': 'device_type_id', 'title': '资产类型',
             'display': True,
             'text': {
                 'tpl': "{n1}",
                 'kwargs': {'n1': '@@device_type_choices'}
             },
             'attrs': {'k1': 'v1', 'nid': '@id','origin':'@device_type_id','name':'device_type_id',
                            'edit-enable':'true','edit-type':'select','global_key':'device_type_choices'}},
            {'q': 'device_status_id', 'title': '状态',
             'display': True,
             'text': {
                 'tpl': "{n1}",
                 'kwargs': {'n1': '@@device_status_choices'}
             },
             'attrs': {'k1': 'v1', 'nid': '@id','origin':'@device_status_id','name':'device_status_id',
                            'edit-enable':'true','edit-type':'select','global_key':'device_status_choices'}},
            {'q':None, 'title': '操作',
             'display': True,
             'text': {
                 'tpl': "<a href='/del?nid={nid}'>删除</a>",
                 'kwargs': {'nid': '@id'}
             },'attrs':{'k1':'v1','k2':'@id'}
             },
        ]
search_config =  [
    {'name': 'cabinet_num', 'text': '机柜号', 'search_type': 'input'},
    {'name': 'device_type_id', 'text': '资产类型', 'search_type': 'select', 'global_name': 'device_type_choices'},
    {'name': 'device_status_id', 'text': '资产状态', 'search_type': 'select', 'global_name': 'device_status_choices'},
]
配置文件示例

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

 

posted @ 2018-03-07 21:24  dion至君  阅读(1053)  评论(0)    收藏  举报