python---CMDB配置管理数据库

前戏:项目目的

是一个运维自动化管理项目:

  为了减少人工干预,降低人员成本
  ---资产管理
  --操作管理

避免人员直接操作服务器,使用后台去统一操作

一:实现方式

(一)Agent基于shell命令实现(在服务器去上安装Agent,在服务器本机定时自动去获取信息,发送到数据库,然后后台获取数据进行处理)

注意:一般我们不会直接将数据直接传递到数据库,会将数据传递到API接口先进行处理,过滤,然后才会发送到数据库。

注意:数据是由服务器agent主动发送至API

 实现方案:

本地执行cmd命令。
方法一:os.system("命令")    不可以返回数据
方法二:subprocess模块,使用进程执行命令,可以获取到数据Popen("命令"),进程.stdout.read()<py2>或者直接getoutput("命令")<py3>
    def agent(self,cmd):
        import subprocess
        try:
            ret = subprocess.getoutput(cmd)
        except AttributeError:
            sub = subprocess.Popen(args=cmd,shell=True,stdout=subprocess.PIPE)
            sub.wait()
            ret = sub.stdout.read()
        return ret
python实现agent

优点:信息采集快,由服务器自己采集信息传递到API

缺点:每台服务器都必须安装Agent


 (二)SSH方法:使用paramiko模块,通过中控机服务器统一去获取指定服务器的信息。

paramiko模块了解

def ssh(self,cmd):
        import paramiko
        #1.创建SSH对象
        ssh = paramiko.SSHClient()
        #2.加上这句话不用担心选yes的问题,会自动选上
        #3.用ssh连接远程主机时,第一次连接时会提示是否继续进行远程连接,选择yes
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",password="密码")
        #执行命令,获取结果到标准输入\出\错误流中
        stdin,stdout,stderr = ssh.exec_command(cmd)
        #4.获取命令结果
        result = stdout.read()
        #5.关闭连接
        ssh.close()
paramiko实现远程命令执行(方法一:使用用户名,密码)
    def ssh(self,cmd):
        import paramiko
        #1.创建SSH对象
        ssh = paramiko.SSHClient()
        #2.加上这句话不用担心选yes的问题,会自动选上
        #用ssh连接远程主机时,第一次连接时会提示是否继续进行远程连接,选择yes
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        #3.获取私钥
        private_key = paramiko.RSAKey.from_private_key_file("文件:其中保存了私钥,用于解密")
        #4.通过私钥去连接远程服务器(前提是自己的公钥已经在对方的authorized_keys文件中,paramiko已实现)
        ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",pkey="私钥private_key")
        #5.执行命令,获取结果到标准输入\出\错误流中
        stdin,stdout,stderr = ssh.exec_command(cmd)
        #6.获取命令结果
        result = stdout.read()
        #7.关闭连接
        ssh.close()
        return result
paramiko使用私钥去登录远程服务器执行命令

优点:不需要为服务器安装agent等软件

缺点:速度慢,适用于服务器少得时候

 (三)saltstack:使用master对slave进行操作,基于列队实现(使用广)

# 1. 安装saltstack
#       rpm --import https://repo.saltstack.com/yum/redhat/6/x86_64/latest/SALTSTACK-GPG-KEY.pub
#
#
"""
        Master: yum install salt-master
       Master准备:
            a. 配置文件,监听本机IP
                vim /etc/salt/master
                interface: 本机IP地址
            b. 启动master
                /etc/init.d/salt-master start


        Slave:  yum install salt-minion
        Slave准备:
            a. 配置文件,连接那个master
                vim /etc/salt/minion
                master: 远程master地址
            b. 启动slave
                /etc/init.d/salt-minion start

2. 创建关系
    查看
    Master:salt-key -L
        Accepted Keys:
        Denied Keys:
        Unaccepted Keys:
            c1.com
            c2.com
            c3.com
        Rejected Keys:
    接受
    Master:salt-key -a c1.com
        Accepted Keys:
            c1.com
            c2.com
        Denied Keys:
        Unaccepted Keys:
            c3.com
        Rejected Keys:


3. 执行命令
    master:
        salt 'c1.com' cmd.run  'ifconfig'

    import salt.client
    local = salt.client.LocalClient()
    result = local.cmd('c2.salt.com', 'cmd.run', ['ifconfig'])

"""
saltstack安装
    def salt(self,cmd):
        import subprocess
        result = subprocess.getoutput("Salt '主机名' cmd.run '"+cmd+"'")

        return result
中控机执行命令:subprocess执行salt
    def salt(self,cmd):

        import salt.client
        local = salt.client.LocalClient()
        result = local.cmd(self.hostname,'cmd.run',[cmd])
        return result[self.hostname]
中控机执行命令:salt.client

优点:快,开发成本低

缺点:依赖saltstack

(四)使用puppet(使用ruby写的,python不易扩展)

优点:自动汇报

缺点:ruby实现

 二:代码实现(客户端)

 

bin目录(含启动文件)

from src.script import client
import os,sys

BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASEDIR)

if __name__ == "__main__":
    client()
start.py启动文件

conf目录(含配置文件)

import os

BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


MODE = "Agent"  #SSH Salt

PLUGINS = {
    'basic':'src.plugins.basic.BasicPlugin',
    'disk':'src.plugins.disk.DiskPlugin',
    'mem': 'src.plugins.mem.MemPlugin',
    # 'nic': 'src.plugins.nic.NicPlugin',
}

# 如果采用SSH方式,则需要配置SSH的KEY和USER
SSH_PRIVATE_KEY = "/home/auto/.ssh/id_rsa"
SSH_USER = "root"
SSH_PORT = 22

# 用于API认证的KEY
KEY = '299095cc-1330-11e5-b06a-a45e60bec08b'
# 用于API认证的请求头
AUTH_KEY_NAME = 'auth-key'

ASSET_API = "http://127.0.0.1:8000/API/asset"


# Agent模式保存服务器唯一ID的文件
CERT_FILE_PATH = os.path.join(BASEDIR, 'conf', 'cert')

TEST_MODE = True
setting.py
...
cert含有主机名信息

lib目录(含有链接库)

class BaseResponse(object):
    def __init__(self):
        self.status = True
        self.message = None
        self.data = None
        self.error = None
response.py定义一种数据格式用于在客户端传递
import json as default_json
from .response import BaseResponse

def conv(obj):
    return obj.__dict__


class Json(object):
    @staticmethod
    def dumps(response):
        return default_json.dumps(response,default=conv)
serialize.py序列化上面的数据,将其变为可以在网络上传递的数据

log目录(记录日志)

client.py定义多种类,不同的方法与API交互
from conf import setting
from .client import *

def client():
    if setting.MODE == 'Agent':  #"Agent"  #SSH Salt
        cli = AutoAgent()
    elif setting.MODE == "SSH":
        cli = AutoSSH()
    elif setting.MODE == "Salt":
        cli = AutoSalt()
    else:
        raise Exception("配置信息出错")
    cli.process()
script.py脚本,根据配置文件去选择实例化那种类,执行process方法获取数据

二级目录:plugins目录

from conf import setting
from lib.response import BaseResponse


def pack(hostname=None):
    response = {}

    for k,v in setting.PLUGINS.items():
        file_name,cls_name = v.rsplit('.',maxsplit=1)
        pk = __import__(file_name,fromlist=True)
        if hasattr(pk,cls_name):
            obj = getattr(pk,cls_name)()
            response[k] = obj.execute(hostname)

    return response
__init__.py定义pack方法去依次获取数据
from conf import setting

class BasePlugin(object):
    def __init__(self):
        mode_list = ['Agent','Salt','SSH']
        self.mode = setting.MODE
        if self.mode not in mode_list:
            raise Exception("请选择正确的管理模式")

    def shell_cmd(self,cmd):
        if self.mode == "SSH":
            ret = self.ssh(cmd)
        elif self.mode == "Salt":
            ret = self.salt(cmd)
        else:
            ret = self.agent(cmd)
        return ret

    def execute(self,hostname=None):
        self.hostname = hostname
        #windows:systeminfo详细信息 ver版本号 linux:cat /etc/issue | grep Linux
        sys_ver = self.shell_cmd("ver")
        pat = "command not found"
        if pat in sys_ver: #是linux...还是继续判断吧,应该不需要了
            sys_ver = self.shell_cmd("head -n 1 /etc/issue")

        if "Linux" in sys_ver:
            return self.linux()
        elif "Windows" in sys_ver:
            return self.windows()
        else:
            raise Exception("只支持linux和windows平台")

    def linux(self):
        raise Exception("请重载linux函数")

    def windows(self):
        raise Exception("请实现windows方法")

    def write(self,output):
        import os
        with open(os.path.join(setting.BASEDIR,'file',"test.txt"),"w") as fp:
            fp.write(output)


    def agent(self,cmd):
        import subprocess
        try:
            ret = subprocess.getoutput(cmd)
        except AttributeError:
            sub = subprocess.Popen(args=cmd,shell=True,stdout=subprocess.PIPE)
            sub.wait()
            ret = sub.stdout.read()
        return ret

    def salt(self,cmd):
        import subprocess
        result = subprocess.getoutput("Salt '主机名' cmd.run '"+cmd+"'")

        return result
        # import salt.client
        # local = salt.client.LocalClient()
        # result = local.cmd(self.hostname,'cmd.run',[cmd])
        # return result[self.hostname]

    def ssh(self,cmd):
        import paramiko
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        private_key = paramiko.RSAKey.from_private_key_file("文件:其中保存了私钥,用于解密")
        ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",pkey="私钥private_key")
        stdin,stdout,stderr = ssh.exec_command(cmd)
        result = stdout.read()
        ssh.close()
        return result
base.py定义基类,实现部分方法
from .base import BasePlugin
import re
from conf import setting
import traceback
from lib.response import BaseResponse

class BasicPlugin(BasePlugin):
    def __init__(self):
        super(BasicPlugin, self).__init__()
        self.Basic_data = {}  # 存放我们获取的数据

    def windows(self):
        response = BaseResponse()
        try:
            # 获取主机名
            output = self.shell_cmd("hostname")
            self.Basic_data['hostname'] = output

            output = self.shell_cmd("ver")
            #获取操作平台
            ret = re.search("(.*)\s*\[",output)
            if ret:
                self.Basic_data['os_platform'] = ret.group(1)

            # 获取系统版本
            ret = re.search("\[(.*)\]",output)
            if ret:
                self.Basic_data['os_version'] = ret.group(1)

            response.data = self.Basic_data
        except Exception as e:
            msg = "%s window memory plugins error: %s"
            response.status = False
            response.error = msg % (self.hostname, traceback.format_exc())  # traceback.format_exc()返回前面错误的信息
        return response

    def linux(self):
        # 获取返回的字符串
        output = self.shell_cmd("查看硬盘")
        # 进行正则匹配,放入Mem_data中
        return self.Basic_data
basic.py获取主板信息
from .base import BasePlugin
from conf import setting
import re,traceback
from lib.response import BaseResponse

class DiskPlugin(BasePlugin):
    def __init__(self):
        super(DiskPlugin, self).__init__()
        self.Disk_data = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}

    def windows(self):
        response = BaseResponse()
        try:
            if setting.TEST_MODE == True:
                import os
                with open(os.path.join(setting.BASEDIR,'file','disk.out'),"r") as fp:
                    output = fp.read()
            else:
                # 获取返回的字符串
                output = self.shell_cmd("wmic logicaldisk")

            response.data = self.parse(output)
        except Exception as e:
            msg = "%s window disk plugin error: %s"
            response.status = False
            response.error = msg % (self.hostname,traceback.format_exc())   #traceback.format_exc()返回前面错误的信息
        return response

    def linux(self):
        # 获取返回的字符串
        output = self.shell_cmd("查看硬盘")
        # 进行正则匹配,放入Mem_data中
        return self.Disk_data

    def parse(self,content):
        response = {}
        result = [] #模拟多台   使用4个\n分割
        for row_line in content.split("\n\n\n\n"):
            result.append(row_line)
        for item in result:
            temp_dict = {}
            for row in item.split('\n'):
                if not row.strip():
                    continue
                if len(row.split(":")) != 2:
                    continue
                key,val = row.split(":")
                name = self.patter_match(key)
                if name:
                    if key == 'Raw Size':
                        val = re.search("(\d+\.\d+)",val.strip())
                        if not val:
                            val = 0
                        else:
                            val = val.group(1)
                    temp_dict[name] = val
            if temp_dict:
                response[temp_dict['slot']] = temp_dict
        return response

    def patter_match(self,key):
        for k,v in self.Disk_data.items():
            if key.startswith(k):
                return v
        return False
disk.py获取硬盘信息
from .base import BasePlugin
from conf import setting
import traceback
from lib.response import BaseResponse

class MemPlugin(BasePlugin):
    def __init__(self):
        super(MemPlugin, self).__init__()
        self.Mem_data = {
            'Size': 'capacity',
            'Locator': 'slot',
            'Type': 'model',
            'Speed': 'speed',
            'Manufacturer': 'manufacturer',
            'Serial Number': 'sn',
        }

    def windows(self):
        response = BaseResponse()
        try:
            if setting.TEST_MODE == True:
                import os
                with open(os.path.join(setting.BASEDIR, 'file', 'memory.out'), "r") as fp:
                    output = fp.read()
            else:
                # 获取返回的字符串
                output = self.shell_cmd("wmic memorychip")

            response.data = self.parse(output)
        except Exception as e:
            msg = "%s window memory plugins error: %s"
            response.status = False
            response.error = msg % (self.hostname, traceback.format_exc())  # traceback.format_exc()返回前面错误的信息
        return response

    def linux(self):
        # 获取返回的字符串
        output = self.shell_cmd("查看内存")
        # 进行正则匹配,放入Mem_data中
        return self.Mem_data


    def parse(self,content):
        response = {}
        result = [] #模拟多台   使用4个\n分割
        for row_line in content.split("Memory Device"):
            result.append(row_line)
        for item in result:
            for row in item.split('\n\t'):
                if not row.strip():
                    continue
                if len(row.split(":")) != 2:
                    continue
                key,val = row.split(":")
                name = self.patter_match(key)
                if name:
                    response[name] = val
        return response

    def patter_match(self,key):
        for k,v in self.Mem_data.items():
            if key.startswith(k):
                return v
        return False
mem.py获取内存信息

file文件目录(含有测试的文件,文件包含各种命令下的数据)

Enclosure Device ID: 32
Slot Number: 0
Drive's postion: DiskGroup: 0, Span: 0, Arm: 0
Enclosure position: 0
Device Id: 0
WWN: 5000C5007272C288
Sequence Number: 2
Media Error Count: 0
Other Error Count: 0
Predictive Failure Count: 0
Last Predictive Failure Event Seq Number: 0
PD Type: SAS
Raw Size: 279.396 GB [0x22ecb25c Sectors]
Non Coerced Size: 278.896 GB [0x22dcb25c Sectors]
Coerced Size: 278.875 GB [0x22dc0000 Sectors]
Firmware state: Online, Spun Up
Device Firmware Level: LS08
Shield Counter: 0
Successful diagnostics completion on :  N/A
SAS Address(0): 0x5000c5007272c289
SAS Address(1): 0x0
Connected Port Number: 0(path0) 
Inquiry Data: SEAGATE ST300MM0006     LS08S0K2B5NV            
FDE Enable: Disable
Secured: Unsecured
Locked: Unlocked
Needs EKM Attention: No
Foreign State: None 
Device Speed: 6.0Gb/s 
Link Speed: 6.0Gb/s 
Media Type: Hard Disk Device
Drive Temperature :29C (84.20 F)
PI Eligibility:  No 
Drive is formatted for PI information:  No
PI: No PI
Drive's write cache : Disabled
Port-0 :
Port status: Active
Port's Linkspeed: 6.0Gb/s 
Port-1 :
Port status: Active
Port's Linkspeed: Unknown 
Drive has flagged a S.M.A.R.T alert : No



Enclosure Device ID: 32
Slot Number: 1
Drive's postion: DiskGroup: 0, Span: 0, Arm: 1
Enclosure position: 0
Device Id: 1
WWN: 5000C5007272DE74
Sequence Number: 2
Media Error Count: 0
Other Error Count: 0
Predictive Failure Count: 0
Last Predictive Failure Event Seq Number: 0
PD Type: SAS
Raw Size: 279.396 GB [0x22ecb25c Sectors]
Non Coerced Size: 278.896 GB [0x22dcb25c Sectors]
Coerced Size: 278.875 GB [0x22dc0000 Sectors]
Firmware state: Online, Spun Up
Device Firmware Level: LS08
Shield Counter: 0
Successful diagnostics completion on :  N/A
SAS Address(0): 0x5000c5007272de75
SAS Address(1): 0x0
Connected Port Number: 0(path0) 
Inquiry Data: SEAGATE ST300MM0006     LS08S0K2B5AH            
FDE Enable: Disable
Secured: Unsecured
Locked: Unlocked
Needs EKM Attention: No
Foreign State: None 
Device Speed: 6.0Gb/s 
Link Speed: 6.0Gb/s 
Media Type: Hard Disk Device
Drive Temperature :29C (84.20 F)
PI Eligibility:  No 
Drive is formatted for PI information:  No
PI: No PI
Drive's write cache : Disabled
Port-0 :
Port status: Active
Port's Linkspeed: 6.0Gb/s 
Port-1 :
Port status: Active
Port's Linkspeed: Unknown 
Drive has flagged a S.M.A.R.T alert : No



Enclosure Device ID: 32
Slot Number: 2
Drive's postion: DiskGroup: 1, Span: 0, Arm: 0
Enclosure position: 0
Device Id: 2
WWN: 50025388A075B731
Sequence Number: 2
Media Error Count: 0
Other Error Count: 1158
Predictive Failure Count: 0
Last Predictive Failure Event Seq Number: 0
PD Type: SATA
Raw Size: 476.939 GB [0x3b9e12b0 Sectors]
Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors]
Coerced Size: 476.375 GB [0x3b8c0000 Sectors]
Firmware state: Online, Spun Up
Device Firmware Level: 1B6Q
Shield Counter: 0
Successful diagnostics completion on :  N/A
SAS Address(0): 0x500056b37789abee
Connected Port Number: 0(path0) 
Inquiry Data: S1SZNSAFA01085L     Samsung SSD 850 PRO 512GB               EXM01B6Q
FDE Enable: Disable
Secured: Unsecured
Locked: Unlocked
Needs EKM Attention: No
Foreign State: None 
Device Speed: 6.0Gb/s 
Link Speed: 6.0Gb/s 
Media Type: Solid State Device
Drive:  Not Certified
Drive Temperature :25C (77.00 F)
PI Eligibility:  No 
Drive is formatted for PI information:  No
PI: No PI
Drive's write cache : Disabled
Drive's NCQ setting : Disabled
Port-0 :
Port status: Active
Port's Linkspeed: 6.0Gb/s 
Drive has flagged a S.M.A.R.T alert : No



Enclosure Device ID: 32
Slot Number: 3
Drive's postion: DiskGroup: 1, Span: 0, Arm: 1
Enclosure position: 0
Device Id: 3
WWN: 50025385A02A074F
Sequence Number: 2
Media Error Count: 0
Other Error Count: 0
Predictive Failure Count: 0
Last Predictive Failure Event Seq Number: 0
PD Type: SATA
Raw Size: 476.939 GB [0x3b9e12b0 Sectors]
Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors]
Coerced Size: 476.375 GB [0x3b8c0000 Sectors]
Firmware state: Online, Spun Up
Device Firmware Level: 6B0Q
Shield Counter: 0
Successful diagnostics completion on :  N/A
SAS Address(0): 0x500056b37789abef
Connected Port Number: 0(path0) 
Inquiry Data: S1AXNSAF912433K     Samsung SSD 840 PRO Series              DXM06B0Q
FDE Enable: Disable
Secured: Unsecured
Locked: Unlocked
Needs EKM Attention: No
Foreign State: None 
Device Speed: 6.0Gb/s 
Link Speed: 6.0Gb/s 
Media Type: Solid State Device
Drive:  Not Certified
Drive Temperature :28C (82.40 F)
PI Eligibility:  No 
Drive is formatted for PI information:  No
PI: No PI
Drive's write cache : Disabled
Drive's NCQ setting : Disabled
Port-0 :
Port status: Active
Port's Linkspeed: 6.0Gb/s 
Drive has flagged a S.M.A.R.T alert : No



Enclosure Device ID: 32
Slot Number: 4
Drive's postion: DiskGroup: 1, Span: 1, Arm: 0
Enclosure position: 0
Device Id: 4
WWN: 50025385A01FD838
Sequence Number: 2
Media Error Count: 0
Other Error Count: 0
Predictive Failure Count: 0
Last Predictive Failure Event Seq Number: 0
PD Type: SATA
Raw Size: 476.939 GB [0x3b9e12b0 Sectors]
Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors]
Coerced Size: 476.375 GB [0x3b8c0000 Sectors]
Firmware state: Online, Spun Up
Device Firmware Level: 5B0Q
Shield Counter: 0
Successful diagnostics completion on :  N/A
SAS Address(0): 0x500056b37789abf0
Connected Port Number: 0(path0) 
Inquiry Data: S1AXNSAF303909M     Samsung SSD 840 PRO Series              DXM05B0Q
FDE Enable: Disable
Secured: Unsecured
Locked: Unlocked
Needs EKM Attention: No
Foreign State: None 
Device Speed: 6.0Gb/s 
Link Speed: 6.0Gb/s 
Media Type: Solid State Device
Drive:  Not Certified
Drive Temperature :27C (80.60 F)
PI Eligibility:  No 
Drive is formatted for PI information:  No
PI: No PI
Drive's write cache : Disabled
Drive's NCQ setting : Disabled
Port-0 :
Port status: Active
Port's Linkspeed: 6.0Gb/s 
Drive has flagged a S.M.A.R.T alert : No



Enclosure Device ID: 32
Slot Number: 5
Drive's postion: DiskGroup: 1, Span: 1, Arm: 1
Enclosure position: 0
Device Id: 5
WWN: 50025385A02AB5C9
Sequence Number: 2
Media Error Count: 0
Other Error Count: 0
Predictive Failure Count: 0
Last Predictive Failure Event Seq Number: 0
PD Type: SATA
Raw Size: 476.939 GB [0x3b9e12b0 Sectors]
Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors]
Coerced Size: 476.375 GB [0x3b8c0000 Sectors]
Firmware state: Online, Spun Up
Device Firmware Level: 6B0Q
Shield Counter: 0
Successful diagnostics completion on :  N/A
SAS Address(0): 0x500056b37789abf1
Connected Port Number: 0(path0) 
Inquiry Data: S1AXNSAFB00549A     Samsung SSD 840 PRO Series              DXM06B0Q
FDE Enable: Disable
Secured: Unsecured
Locked: Unlocked
Needs EKM Attention: No
Foreign State: None 
Device Speed: 6.0Gb/s 
Link Speed: 6.0Gb/s 
Media Type: Solid State Device
Drive:  Not Certified
Drive Temperature :28C (82.40 F)
PI Eligibility:  No 
Drive is formatted for PI information:  No
PI: No PI
Drive's write cache : Disabled
Drive's NCQ setting : Disabled
Port-0 :
Port status: Active
Port's Linkspeed: 6.0Gb/s 
Drive has flagged a S.M.A.R.T alert : No




Exit Code: 0x00
disk.out
Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: 1024 MB
    Form Factor: DIMM
    Set: None
    Locator: DIMM #0
    Bank Locator: BANK #0
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown

Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: No Module Installed
    Form Factor: DIMM
    Set: None
    Locator: DIMM #1
    Bank Locator: BANK #1
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown

Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: No Module Installed
    Form Factor: DIMM
    Set: None
    Locator: DIMM #2
    Bank Locator: BANK #2
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown

Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: No Module Installed
    Form Factor: DIMM
    Set: None
    Locator: DIMM #3
    Bank Locator: BANK #3
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown

Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: No Module Installed
    Form Factor: DIMM
    Set: None
    Locator: DIMM #4
    Bank Locator: BANK #4
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown

Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: No Module Installed
    Form Factor: DIMM
    Set: None
    Locator: DIMM #5
    Bank Locator: BANK #5
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown

Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: No Module Installed
    Form Factor: DIMM
    Set: None
    Locator: DIMM #6
    Bank Locator: BANK #6
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown

Memory Device
    Total Width: 32 bits
    Data Width: 32 bits
    Size: No Module Installed
    Form Factor: DIMM
    Set: None
    Locator: DIMM #7
    Bank Locator: BANK #7
    Type: DRAM
    Type Detail: EDO
    Speed: 667 MHz
    Manufacturer: Not Specified
    Serial Number: Not Specified
    Asset Tag: Not Specified
    Part Number: Not Specified
    Rank: Unknown
memory.out

.....

 三:代码实现(API接口Django实现)

API目录

from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.http import HttpResponse,JsonResponse
from repository import models
from API import config
from utils import auth
import importlib
import json


class AssetView(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(AssetView, self).dispatch(request, *args, **kwargs)

    @method_decorator(auth.api_auth)
    def get(self,request,*args,**kwargs):
        pass

    @method_decorator(auth.api_auth)
    def post(self,request,*args,**kwargs):
        '''
        获取传递的数据,进行添加,修改
        :param request:
        :param args:
        :param kwargs:
        :return:1000成功,1001,接口授权失败,1002数据库中资产不存在
        '''
        #获取所有信息
        # print(request.body.decode('utf-8')) #带转义符的字符串
        server_info = json.loads(request.body.decode("utf-8"))
        # print(server_info)  #字符串
        server_info = json.loads(server_info)
        #获取主机名
        hostname = server_info['basic']['data']['hostname']
        ret = {"code":1000,"message":'[%s]更新完成'%hostname}

        #根据主机名去数据库中获取相关信息
        server_obj = models.Server.objects.filter(hostname=hostname).select_related('asset').first()
        if not server_obj:
            ret['code'] = 1002
            ret['message'] = '[%s]资产不存在'
            return JsonResponse(ret)

        for k,v in config.PLUGINS_DICT.items():
            module_path,cls_name = v.rsplit('.',1)
            cls = getattr(importlib.import_module(module_path),cls_name)
            response = cls.process(server_obj,server_info,None)
            if not response.status:
                ret['code'] = 1003
                ret['message'] = "[%s]资产更新异常" % hostname
            if hasattr(cls,'update_last_time'):
                cls.update_last_time(server_obj,None)

        return JsonResponse(ret)
views.py实现数据处理MBV实现获取数据和发送数据

二级目录service

from repository import models
import traceback,datetime
from utils.response import BaseResponse
from utils.agorithm import *

class HandleBasic(object):
    @staticmethod
    def process(server_obj,server_info,user_obj):
        response = BaseResponse()

        try:
            log_list = []
            main_board = server_info['basic']['data']
            if main_board['os_platform'] != server_obj.os_platform:
                log_list.append("系统由%s变更为%s"%(server_obj.os_platform,main_board['os_platform']))
                server_obj.os_platform = main_board['os_platform']

            if main_board['os_version'] != server_obj.os_version:
                log_list.append("系统由%s变更为%s"%(server_obj.os_version,main_board['os_version']))
                server_obj.os_version = main_board['os_version']

            server_obj.save()
            if log_list:
                models.AssetRecord.objects.create(asset_obj=server_obj.asset,creator=user_obj,content=';'.join(log_list))

        except Exception as e:
            response.status = False
            models.ErrorLog.objects.create(asset_obj=server_obj.asset,title="basic-run",
                                           content=traceback.format_exc())

        return response

    @staticmethod
    def update_last_time(server_obj,user_obj):
        response = BaseResponse()
        try:
            current_date = datetime.date.today()
            server_obj.asset.latest_date = current_date
            server_obj.asset.save()
            models.AssetRecord.objects.create(asset_obj=server_obj.asset,creator=user_obj,content="资产汇报")
        except Exception as e:
            response.status = False
            models.ErrorLog.objects.create(asset_obj=server_obj.asset,title="basic-run",
                                           content=traceback.format_exc())

        return response

class HandleDisk(object):
    @staticmethod
    def process(server_obj,server_info,user_obj):
        response = BaseResponse()
        try:
            disk_info = server_info['disk']
            if not disk_info['status']:
                response.status = False
                models.ErrorLog.objects.create(
                    asset_obj=server_obj.asset,
                    title="disk-plugins",
                    content=disk_info['error']
                )
                return response
            client_disk_dict = disk_info['data']

            #获取服务器下的所有硬盘设备
            disk_obj_list = models.Disk.objects.filter(server_obj=server_obj)
            #获取插槽
            disk_slots = map(lambda x:x, (item.slot for item in disk_obj_list))

            update_list = get_intersection(set(client_disk_dict.keys()),set(disk_slots))
            add_list = get_exclude(client_disk_dict.keys(),update_list)
            del_list = get_exclude(disk_slots,update_list)

            HandleDisk._add_disk(add_list,client_disk_dict,server_obj,user_obj)
            HandleDisk._update_disk(update_list,disk_obj_list,client_disk_dict,server_obj,user_obj)
            HandleDisk._del_disk(del_list,disk_obj_list,server_obj,user_obj)

        except Exception as e:
            response.status = False
            models.ErrorLog.objects.create(
                asset_obj=server_obj.asset,
                title="disk-plugins",
                content=traceback.format_exc()
            )

        return response

    @staticmethod
    def _add_disk(add_list,client_disk_dict,server_obj,user_obj):
        for item in add_list:
            cur_disk_dict = client_disk_dict[item]
            log_str = "[新增硬盘]插槽位{slot};容量为{capacity};硬盘类型为{pd_type};型号为{model}".format(**cur_disk_dict)
            cur_disk_dict['server_obj'] = server_obj
            models.Disk.objects.create(**cur_disk_dict)
            models.AssetRecord.objects.create(
                asset_obj=server_obj.asset,creator=user_obj,content=log_str
            )

    @staticmethod
    def _del_disk(del_lsit,disk_objs,server_obj,user_obj):
        for item in disk_objs:
            if item.slot in del_lsit:
                log_str = "[移除硬盘]插槽位{slot};容量为{capacity};硬盘类型为{pd_type};型号为{model}".format(item.__dict__)
                item.delete()
                models.AssetRecord.objects.create(
                    asset_obj = server_obj.asset,
                    creator = user_obj,
                    content = log_str
                )

    @staticmethod
    def _update_disk(update_list,disk_objs,client_disk_dict,server_obj,user_obj):
        for item in disk_objs:
            if item.slot in update_list:
                log_list = []

                new_model = client_disk_dict[item.slot]['model']
                if item.model != new_model:
                    log_list.append("[更新硬盘]插槽为%s:型号由%s变更为%s"%(item.slot,item.model,new_model))
                    item.model = new_model

                new_capacity = client_disk_dict[item.slot]['capacity']
                new_capacity = float(new_capacity)

                if new_capacity != item.capacity:
                    log_list.append("[更新硬盘]插槽为%s:容量由%sGB变更为%sGB"%(item.slot,item.capacity,new_capacity))
                    item.capacity = new_capacity

                new_pd_type = client_disk_dict[item.slot]['pd_type']
                if item.pd_type != new_pd_type:
                    log_list.append("[更新硬盘]插槽为%s:类型由%s变更为%s"%(item.slot,item.pd_type,new_pd_type))
                    item.pd_type = new_pd_type

                item.save()
                if log_list:
                    models.AssetRecord.objects.create(
                        asset=server_obj.asset,
                        creator=user_obj,
                        content=';'.join(log_list)
                    )
asset.py资产管理

utils工具目录

import hashlib,time,redis
from CMDBAPI import settings
from django.http import JsonResponse

def api_auth_method(request):
    auth_key = settings.KEY
    auth_name = settings.AUTH_KEY_NAME
    auth_data = request.META['HTTP_'+auth_name.upper()]

    auth_val,auth_time = str(auth_data).split('|')
    #1:时间上的验证
    if float(auth_time) + 10 < time.time():
        return False
    #2:数据是否过期,先从redis获取数据{auth_val:auth_time}

    rd = redis.Redis(host="localhost",port=6379)

    r_time = rd.get(auth_val)
    if r_time:
        return False

    #3:验证数据是否被修改
    ha = hashlib.md5(auth_key.encode("utf-8"))
    ha.update(("%s|%s" % (auth_key, auth_time)).encode("utf-8"))
    encryption = ha.hexdigest()
    if encryption != auth_val:
        return False

    rd.set(auth_val,auth_time)

    return True


def api_auth(func):
    def inner(request,*args,**kwargs):
        if not api_auth_method(request):
            return JsonResponse({"code":1001,"message":"API授权失败"},json_dumps_params={'ensure_ascii': False})
        return func(request,*args,**kwargs)
    return inner
auth.py权限认证,对传递的数据进行过滤,其中使用redis
def get_intersection(*args):
    '''
    获取集合的并集
    :param args:  set集合
    :return:  并集列表
    '''
    base = args[0]
    result = base.intersection(*args)
    return list(result)

def get_exclude(total,part):
    '''
    获取差集
    :param total:
    :param part:
    :return:
    '''
    result = []
    for item in total:
        if item in part:
            pass
        else:
            result.append(item)
    return result
agorithm.py处理数据,获取差并集来代表数据更新删除,添加

四:数据库实现

class UserProfile(models.Model):
    '''
    用户信息
    '''
    name = models.CharField("姓名",max_length=32)
    email = models.EmailField("邮箱")
    phone = models.CharField("座机",max_length=32)
    mobile = models.CharField("手机",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("用户名",max_length=64)
    password = models.CharField("密码",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):
    '''
    资产标签:以前是做什么的,web服务器....,一个资产可以有多个标签,一个标签可以有端个资产
    '''
    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=32,null=True,blank=True)
    cabinet_order = models.CharField("机柜中的序号",max_length=32,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)
    manafacturer = models.CharField("制造商",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=32,null=True,blank=True)
    os_version = models.CharField("系统版本",max_length=32,null=True,blank=True)

    cpu_count = models.IntegerField("CPU个数",null=True,blank=True)
    cpu_physical = 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)
    idaddrs = 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)
    manafacturer = 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):
    '''
    资产变更记录
    '''
    asset_obj = models.ForeignKey("Asset",related_name='ar')
    content = models.TextField(null=True)
    creator = models.ForeignKey("UserProfile",null=True,blank=True) #creator为空,代表是资产汇报的数据
    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):
    '''
    错误日志
    '''
    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
数据库表结构

 五:后台管理(实现动态编辑数据)

from django.shortcuts import render,HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.db import transaction
from django.views import View
from repository import models
import json,traceback
# Create your views here.

class Assert(View):
    def get(self,request,*args,**kwargs):

        return render(request,"asset.html")

class AssertJson(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(AssertJson, self).dispatch(request,*args,**kwargs)

    def get(self,request,*args,**kwargs):
        config_list = [
            {
                'field': None,
                'display': True,
                'title': "选项",
                'text': {'content': "<input type='checkbox'/>", 'kwargs': {}},
                'attrs': {}
            },
            {
                'field': "id",
                'display': False,
                'title': "ID",
                'text': {},
                'attrs': {}
            },
            {
                'field':"cabinet_num",
                'display':True,
                'title':"机柜号",
                'text': {"content":"{n}","kwargs":{'n':"@cabinet_num"}},
                'attrs': {'name':'cabinet_num','origin':'@cabinet_num','edit-able': True, 'edit-type': 'input'}
            },
            {
                'field': "cabinet_order",
                'display': True,
                'title': "机柜位置",
                'text':{"content":"{n}","kwargs":{'n':"@cabinet_order"}},
                'attrs': {'name':'cabinet_order','origin':'@cabinet_order','edit-able': True, 'edit-type': 'input'}
            },
            {
                'field': "device_type_id",
                'display': True,
                'title': "资产类型",
                'text': {"content": "{n}", "kwargs": {'n': "@@device_type_choices"}},
                'attrs': {'name':'device_type_id','origin':'@device_type_id','edit-able': True, 'edit-type': 'select','global-name':'device_type_choices'}
            },
            {
                'field': "device_status_id",
                'display': True,
                'title': "设备状态",
                'text': {"content": "{n}", "kwargs": {'n': "@@device_status_choices"}},
                'attrs': {'name':'device_status_id','origin':'@device_status_id','edit-able': True, 'edit-type': 'select','global-name':'device_status_choices'}
            },
            {
                'field': "idc__id",
                'display': False,
                'title': "IDC",
                'text': {},
                'attrs': {}
            },
            {
                'field': "idc__name",
                'display': True,
                'title': "IDC机房",
                'text': {"content": "{n}", "kwargs": {'n': "@idc__name"}},
                'attrs': {'name': 'idc_id', 'origin': '@idc__id', 'edit-able': True,'edit-type': 'select', 'global-name': 'idc_choices'}
            },
            {
                'field': None,
                'display': True,
                'title': "操作",
                "text":{"content":"<a href='/asset.html/{m}'>{n}</a>","kwargs":{'n':"点击查看","m":"@id"}},
                'attrs': {}
            },
        ]

        #获取数据库中的数据
        req_list = []
        for item in config_list:
            if item['field']:
                req_list.append(item['field'])
        content = models.Asset.objects.all().values(*req_list)
        content = list(content)

        #获取类型
        global_data = {
            'device_type_choices':models.Asset.device_type_choices,
            'device_status_choices':models.Asset.device_status_choices,
            'idc_choices':list(models.IDC.objects.values_list('id','name')),
        }

        response = {
            'table_config':config_list,
            'content':content,
            'global_data':global_data,
        }
        return HttpResponse(json.dumps(response))

    def put(self,request,*args,**kwargs):
        ret = {
            'status':True,
            'message':None,
        }
        data = json.loads(request.body.decode("utf-8"))
        try:
            with transaction.atomic():
                AssetObj = models.Asset.objects
                for item in data:
                    AssetObj.filter(id=item['id']).update(**item)
        except Exception as e:
            ret['status'] = False
            ret['message'] = traceback.format_exc()
        return HttpResponse(json.dumps(ret))
Django后台View视图代码:注意配置数据的格式
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/statics/plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css">
</head>
<body>
    <div style="width: 1000px;margin: 0 auto;">
        <h1>资源列表</h1>
        <div class="btn-group" role="group">
            <button id="idCheckAll" type="button" class="btn btn-default">全选</button>
            <button id="idReverseAll" type="button" class="btn btn-default">反选</button>
            <button id="idCancelAll" type="button" class="btn btn-default">取消</button>
            <button id="idEditMode" type="button" class="btn btn-default">进入编辑模式</button>
            <button type="button" class="btn btn-default">批量删除</button>
            <button id="idSave" type="button" class="btn btn-default">保存</button>
            <button type="button" class="btn btn-default">添加</button>
        </div>
        <table class="table table-bordered">
            <thead id="table_th"></thead>
            <tbody id="table_tb"></tbody>
        </table>
    </div>

</body>
</html>

<script src="/statics/js/jquery.js"></script>
<script src="/statics/js/nbplugin.js"></script>
<script>
    $(function(){
        $.NB("/Backend/asset-join.html");
    })
</script>
Html前端代码
(function(){
    var requestUrl = null;

    /*自定义字符串格式化函数*/
    String.prototype.format= function(kwargs){
        var ret=this.replace(/\{(\w+)\}/g,function(g,g1){
            return kwargs[g1];
        })
        return ret
    }

    function bindSave(){
        $("#idSave").click(function(){
            var postList = [];
            var flag = false;    //判断是否有数据被修改
            //先找到修改过的行
            $("#table_tb").find("tr[has-edit='true']").each(function(){
               var temp = {};
               var id = $(this).attr("row-id");
               $(this).find("td[edit-able='true']").each(function(){
                   var origin = $(this).attr("origin");
                   var newVal = $(this).attr("new-val");
                   if(newVal && newVal != origin){
                       var name = $(this).attr("name");
                       temp[name] = newVal;
                       flag = true
                   }
               });

               //如果被修改过,添加到postList中
               if(flag){
                   temp['id'] = id;
                   postList.push(temp);
               }

            });

            if(flag){
                $.ajax({
                    'url':requestUrl,
                    'type':'PUT',   //put表示更新数据
                    'data':JSON.stringify(postList),
                    'dataType':"json",
                    success:function(response){
                        if(response.status){
                            alert("更新成功");
                            init();
                        }else{
                            alert("更新失败:"+response.message);
                        }
                    }
                })
            }
        })
    }

    function bindReverse(){
        $("#idReverseAll").click(function(){
            var editing = $("#idEditMode").hasClass("btn-warning");
            $("#table_tb").find(":checkbox").each(function(){
                if(editing){
                    //若是点击了编辑模式,则是需要进入或退出编辑模式
                    if($(this).prop("checked")){
                        $tr = $(this).parents("tr");
                        trOutEditMode($tr);
                        $(this).prop("checked",false);
                    }else{
                        $tr = $(this).parents("tr");
                        trIntoEditMode($tr);
                        $(this).prop("checked",true);
                    }
                }else{
                    //只需要修改复选框按钮状态
                    $(this).prop("checked",!$(this).prop("checked"));
                }
            })
        })
    }

    function bindCancelAll(){
        $("#idCancelAll").click(function(){
            var editing = $("#idEditMode").hasClass("btn-warning");
            $("#table_tb").find(":checkbox").each(function(){
                if(editing){
                    if($(this).prop("checked")){
                        $tr = $(this).parents("tr");
                        trOutEditMode($tr);
                        $(this).prop("checked",false);
                    }
                }else{
                    if($(this).prop("checked")){
                        $(this).prop("checked",false);
                    }
                }
            });
        });
    }

    function bindCheckAll() {
        $("#idCheckAll").click(function(){
            var editing = $("#idEditMode").hasClass("btn-warning");
            if(editing){
                $("#table_tb").find(":checkbox").each(function(){
                    if($(this).prop("checked")){
                        //无操作
                    }else{
                        $tr = $(this).parents("tr");
                        trIntoEditMode($tr);
                        $(this).prop("checked",true);
                    }
                })
            }else{
                $("#table_tb").find(":checkbox").each(function(){
                    if(!$(this).prop("checked")){
                        $(this).prop("checked",true);
                    }
                })
            }
        })
    }

    function bindCheckBox(){
        $("#table_tb").on('click',':checkbox',function(){
            if($("#idEditMode").hasClass("btn-warning")){
                var ck = $(this).prop("checked");
                $currentTr = $(this).parents("tr");
                if(ck){
                    //进入编辑模式
                    trIntoEditMode($currentTr);
                }else{
                    //退出编辑模式
                    trOutEditMode($currentTr);
                }
            }
        })
    }

    function trIntoEditMode($tr){
        $tr.addClass("success");    //添加样式
        $tr.attr("has-edit","true");    //代表进入了编辑模式,传递数据的时候会去看他
        $tr.children().each(function(){
            var edit_enable = $(this).attr("edit-able");
            var edit_type = $(this).attr("edit-type");
            if(edit_enable == "true"){
                if(edit_type == "select"){
                    var global_name = $(this).attr("global-name");
                    var origin = $(this).attr("origin");

                    var tag = document.createElement('select');
                    tag.className = "form-control";
                    $.each(window[global_name],function(k,v){
                        var optag = document.createElement("option");
                        optag.setAttribute("value",v[0]);
                        optag.innerHTML = v[1];
                        tag.append(optag);
                    });

                    $(tag).val(origin);

                    $(this).html(tag);
                }else if(edit_type == "input"){
                    var innerText = $(this).text();
                    var tag = document.createElement("input");
                    tag.className = "form-control";
                    tag.value = innerText;
                    $(this).html(tag)
                }
            }
        })
    }

    function trOutEditMode($tr) {
        $tr.removeClass("success");    //添加样式

        $tr.children().each(function(){
            var edit_enable = $(this).attr("edit-able");
            var edit_type = $(this).attr("edit-type");

            if(edit_enable == "true"){
                if(edit_type == "select"){
                    var $select = $(this).children().first();
                    var newId = $select.val();
                    var newText = $select[0].selectedOptions[0].text;

                    $(this).attr("new-val",newId);
                    $(this).html(newText);
                }else if(edit_type == "input"){
                    var $input = $(this).children().first();
                    var inputValue = $input.val();
                    $(this).html(inputValue);
                    $(this).attr("new-val",inputValue);
                }
            }
        })
    }

    function bindEditMode(){
        $("#idEditMode").click(function(){
            var editstatus = $(this).hasClass("btn-warning");
            if(!editstatus){
                /*进入编辑模式*/
                $(this).addClass("btn-warning");
                $(this).text("退出编辑模式");

                $("#table_tb").find(":checked").each(function(){
                    var $tr = $(this).parents("tr");
                    trIntoEditMode($tr);
                })
            }else{
                /*退出编辑模式*/
                $(this).removeClass("btn-warning");
                $(this).text("进入编辑模式");

                $("#table_tb").find(":checked").each(function(){
                    var $tr = $(this).parents("tr");
                    trOutEditMode($tr);
                })
            }
        })
    }

    function init(){
        $("#table_th").empty();
        $("#table_tb").empty();
        $.ajax({
            url:requestUrl,
            type:"get",
            dataType:"json",
            success:function(response){
                initGlobal(response.global_data);
                initHeader(response.table_config);
                intiContent(response.table_config,response.content);
            }
        })
    }

    function initGlobal(gloable_data) {
        $.each(gloable_data,function(k,v){
            window[k] = v
        })
    }

    function initHeader(table_config){
        var tr = document.createElement("tr");
        $.each(table_config,function(k,v){
            if(v.display){
                var th = document.createElement("th");
                th.innerHTML = v.title;
                tr.append(th);
            }
        })
        $("#table_th").append(tr);
    }

    function intiContent(table_config,content){
        $.each(content,function(k,item){
            var tr = document.createElement("tr");
            $.each(table_config,function(k1,tit){
                if(tit.display){
                    var td = document.createElement("td");
                    /*生成文本信息*/
                    var kwargs = {}; /*将格式化的信息放在该对象中保存,之后我们生成文本信息的时候直接从这里面去取*/
                    $.each(tit.text.kwargs,function(k2,v2) {
                        if(v2.substring(0,2) == "@@"){
                            var index = item[tit.field];
                            var global_name = v2.substring(2,v2.length);
                            kwargs[k2] = getValueFromGlobalByID(global_name,index)
                        }else if(v2[0] == "@"){
                            kwargs[k2] = item[v2.substring(1,v2.length)]
                        }else{
                            kwargs[k2] = v2
                        }
                    });

                    /*设置属性*/
                    $.each(tit.attrs,function(k3,v3){
                        if(v3[0] == "@"){
                            td.setAttribute(k3,item[v3.substring(1,v3.length)])
                        }else{
                            td.setAttribute(k3,v3)
                        }
                    })

                    /*内联代码*/
                    td.innerHTML = tit.text.content.format(kwargs);
                    tr.append(td)
                }
            })

            $(tr).attr("row-id",item['id'])
            $("#table_tb").append(tr)
        })
    }

    function getValueFromGlobalByID(global_name,index){
        var ret = null;
        $.each(window[global_name],function(k,v){
            if(v[0] == index){
                ret = v[1]
                return false
            }
        })
        return ret
    }

    jQuery.extend({
        'NB':function (url){
            requestUrl = url;
            init();
            bindEditMode();
            bindCheckBox();
            bindCheckAll();
            bindCancelAll();
            bindReverse();
            bindSave();
        },
    })
})()
自定义js插件:实现动态获取,编辑数据

六:CMDB项目总结

1. 四种采集资产的方式(掌握3种)
    唯一标识的处理(选用主机名<人为定制规则>,因为其他的也是不具有唯一性,SN码在虚拟机上是一致的)
2. API
    API验证(时间验证,同一时间请求过滤,加密密匙)3次验证,(同加密cookie+时间限制+访问记录)
    数据库表设计
3. 后台管理
    js自定制插件(前端+后端配置),减少CURD操作

 

posted @ 2018-05-19 15:00  山上有风景  阅读(1800)  评论(0编辑  收藏  举报