一、远程服务器资产信息采集方案


实现方案一:agent——server服务端模式

应用场景:多应用于服务器数量多情况下,效率比ssh方式高

  • 客户端:

################### 方式一:Agent,每一台服务器一份 ####################
import subprocess
v1 = subprocess.getoutput('ipconfig')   # 开启一个子进程
value1= v1[20:30]v2 = subprocess.getoutput('dir')   # 开启一个子进程
value2= v2[0:5]import requests
url = 'http://127.0.0.1:8001/asset.html'
response = requests.post(url, data={'k1':value1, 'k2':value2})  # 发送数据
print(response)
#
  • 服务端:
 url(r'^asset.html$', views.asset),
from django.shortcuts import render,HttpResponsedef asset(request):if request.method == "POST":print(request.POST)# 写入到数据return HttpResponse('1002')else:return HttpResponse('姿势不对')

实现方案二:通过SSH远程连接服务器,使用Paramiko创建中控机

pip3 install paramiko

  • 应用场景:服务器数量不多的时候,运行效率相对低,但维护简单,不需要每台服务安装客户端
  • 客户端:
# #################### 方式二:Paramiko,中控机放一份 ####################
"""
- 远程连接服务器,执行命令,获取结果
- 将结果发送API
192.168.11.98
"""
import paramiko# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接远程服务器
ssh.connect(hostname='192.168.121.128', port=22, username='lh', password='152303832')
# 执行命令
stdin, stdout, stderr = ssh.exec_command('ls')
# 获取命令结果
result = stdout.read()
# 关闭连接
ssh.close()
value = result[0:10]
print(value)url = "http://127.0.0.1:8001/asset.html"
import requestsresponse = requests.post(url, data={'k1': value, 'k2': value})
print(response.text)
  • 服务端:与agent服务端相同

实现方案三:使用saltStack开源软件(Python开发)

内部原理:采用消息队列实现,而非ssh,

修改root用户密码:sudo passwd root
切换到root用户:su root
安装软件要用root权限

分为master和salve两部分:

master部分:
- yum install salt-master # 安装master
- interface: 192.168.121.128 # 对配置文件进行配置 /etc/salt/master, ip地址为master所在的服务器地址
- service salt-master start # 启动服务

salve部分:

  • yum install salt-minion
  • 配置:master的 ip地址 # 对配置文件进行配置 /etc/salt/minion
  • service salt-minion start

再对所有savle进行授权操作:
salt-key -L # 查看连接的salve
salt-key -A # 将所有的salve进行授权,可以实现master控制所有的被授权过的salve所在的服务器

最后执行命令:在master服务器上执行: salt “*” cmd.run “ifconfig” # 表示所有salve服务器执行ifconfig命令

  • python代码实现:
    v3 = subprocess.getoutput(‘salt “*” cmd.run “ifconfig” ‘) # 开启一个子进程在本地运行cmd命令

二、采集器部分开发


目录结构:

三、 高级配置文件处理


可执行文件:start.py

import os
os.environ['USER_SETTINGS'] = "config.settings"  # 将用户级别的配置文件路径添加到环境变量中from lib.conf.config import settings    # 备注:需要将该导入放置在添加环境变量语句后面,否则报错
print(settings.USER)

用户自定义配置文件:settings

"""
用户自定义配置文件
"""
USER = 'lh' # 服务器登陆信息
PWD = '152303832'

内置配置文件:global_settings.py

"""
内置配置文件
"""EMAIL = '152303832@qq.com'

配置文件整合:config.py

"""
整合用户配置文件和内置配置文件
"""
import os
import importlib
from . import global_settingsclass Settings(object):def __init__(self):######## 找到默认内置配置文件 ########for name in dir(global_settings):   # 获得模块中的所有属性名列表if name.isupper():value = getattr(global_settings, name)  # 反射获得模块中的属性值setattr(self, name, value)  # 为传入的参数对象添加该属性名及属性值# ######## 找到自定义配置 ######### 根据字符串导入模块settings_module = os.environ.get('USER_SETTINGS') # 获得环境变量(内存)中的自定义配置文件的路径值if not settings_module:returncustom_settings = importlib.import_module(settings_module)    # 根据字符串导入应对的模块for name in dir(custom_settings):if name.isupper():value = getattr(custom_settings, name)setattr(self, name, value)settings = Settings()   # 将实例化的对象作为属性值

三、 CMDB可插拔插件制作


settings.py 配置文件

"""
用户自定义的配置文件
"""
USER = 'lh'  # 服务器登陆信息
PWD = '152303832'import osBASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))# ##############插件所需的配置参数################
MODE = 'AGENT'  # 采用agent模式采集服务器信息
# MODE = 'SALT'  # 采用salt模式采集服务器信息
# MODE = 'SSH'  # 采用SSH模式采集服务器信息DEBUG = TrueSSH_USER = 'root'  # 连接远程服务器的用户名
SSH_PWD = 'root'  # 连接远程服务器的密码
SSH_KEY = '/XX/XX/XX'  # 通过公钥私钥来连接远程服务器实现免密登陆
SSH_PORT = 22PLUGINS_DICT = {  # 插件字典,通过字符串导入模块'basic': "src.plugins.basic.Basic",'board': "src.plugins.board.Board",'cpu': "src.plugins.cpu.Cpu",'disk': "src.plugins.disk.Disk",'memory': "src.plugins.memory.Memory",'nic': "src.plugins.nic.Nic",
}# api接口 url地址
# API = "http://www.oldboyedu.com"
API = "http://127.0.0.1:8000/api/asset.html"# 用于服务器唯一标识符,防止服务器数量出现叠加错误
CERT_PATH = os.path.join(BASEDIR, 'config', 'cert')

config.py 配合配置文件

"""
整合用户配置文件和内置配置文件
"""
import os
import importlib
from . import global_settingsclass Settings(object):def __init__(self):######## 找到默认内置配置文件 ########for name in dir(global_settings):if name.isupper():value = getattr(global_settings, name)  # 反射获得模块中的属性值setattr(self, name, value)  # 为传入的参数对象添加该属性名及属性值# ######## 找到自定义配置 ######### 根据字符串导入模块settings_module = os.environ.get('USER_SETTINGS')  # 获得环境变量(内存)中的自定义配置文件的路径值if not settings_module:returncustom_settings = importlib.import_module(settings_module)  # 根据字符串导入应对的模块for name in dir(custom_settings):if name.isupper():value = getattr(custom_settings, name)setattr(self, name, value)settings = Settings()  # 将实例化的对象作为属性值

start.py启动文件

import os
import sys# 程序启动入口文件
os.environ['USER_SETTINGS'] = "config.settings"  # 将用户级别的配置文件路径添加到环境变量中from lib.conf.config import settings  # 备注:需要将该导入放置在添加环境变量语句后面,否则报错# print(settings.USER)BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASEDIR)from src import scriptif __name__ == '__main__':script.run()

script.py脚本文件:

from lib.conf.config import settings
from .client import Agent
from .client import SSHSALTdef run():"""根据配置文件中的内容选择不同的采集方式:param object::return:"""if settings.MODE == 'AGENT':obj = Agent()else:obj = SSHSALT()obj.excute()

client.py客户端:

import requests
from lib.conf.config import settings
from src.plugins import PluginManager
import json
from concurrent.futures import ThreadPoolExecutorclass Base(object):"""负责往api发送数据"""def post_asset(self, server_info):# 将数据转换成json字符串格式发送requests.post(settings.API, json=server_info)  # 数据封装在body中: 会在源码中自动转换 json.dumps(server_info)# headers= {'content-type':'application/json'}# request.body   # 需从body中取出数据# json.loads(request.body)class Agent(Base):"""用agent方式采集数据并提交到api"""def excute(self):servier_info = PluginManager().exec_plugin()  # 采集数据# 唯一标识符处理hostname = servier_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:# 用文件中的主机名覆盖被用户修改过的主机名,防止出现主机重复导致数量叠加错误servier_info['basic']['data']['hostname'] = certnameself.post_asset(servier_info)  # 子类对象调用父类方法来发送数据class SSHSALT(Base):"""用SSH方式和SALT方式采集数据和发送"""def get_host(self):  # 该方式先获取未采集过数据的主机列表response = requests.get(settings.API)result = json.load(response.text)  # "{status:'True',data: ['c1.com','c2.com']}"if result['status']:return Nonereturn result['data']# 执行服务器信息采集,并将该信息发送给APIdef run(self, host):server_info = PluginManager(host).exec_plugin()  # 该两种采集方式都需传入主机host信息self.post_asset(server_info)# 基于线程池实现并发采集资产def excute(self):host_list = self.get_host()# 开启线程池并发任务,一次使用10个线程同时完成任务即可,多了会占用更多的系统资源pool = ThreadPoolExecutor(10)for host in host_list:pool.submit(self.run, host)  # 提交要执行的任务及对应的参数

_init_.py文件:

from lib.conf.config import settings
import importlib
import tracebackclass PluginManager(object):def __init__(self, hostname=None):  # 为agent/salt模式预留的主机名参数值self.hostname = hostnameself.plugin_dict = settings.PLUGINS_DICTself.mode = settings.MODE  # 采集模式self.debug = settings.DEBUGif self.mode == 'SSH':self.ssh_user = settings.SSH_USERself.ssh_port = settings.SSH_PORTself.ssh_pwd = settings.SSH_PWDself.ssh_key = settings.SSH_KEYdef exec_plugin(self):"""获取所有插件,并执行插件中的方法获得返回值:return:"""response = {}for k, v in self.plugin_dict.items():#  'basic': "src.plugins.basic.Basic",ret = {'stauts': True, 'data': None}try:module_path, class_name = v.rsplit('.', 1)  # 切分字符串获得模块路径和类名m = importlib.import_module(module_path)  # 根据字符串获得模块cls = getattr(m, class_name)  # 通过类名字符串,反射获得模块中的类if hasattr(cls, 'initial'):obj = cls.initial()else:obj = cls()result = obj.process(self.command, self.debug)  # result = "根据v获取类,并执行其方法采集资产"ret['data'] = resultexcept Exception as e:ret['stauts'] = False# traceback.format_exc()获得具体的错误信息   k表示插件名称ret['data'] = '[%s][%s]采集数据出现错误:%s' % (self.hostname if self.hostname else 'AGENT', k, traceback.format_exc())response[k] = retreturn response######判断采集方法############def command(self, cmd):if self.mode == 'AGENT':return self.__agent(cmd)elif self.mode == 'SSH':return self.__ssh(cmd)elif self.mode == 'SALT':return self.__salt(cmd)else:raise Exception('模式只能是 AGENT/SSH/SALT')########## 执行对应的采集方法##########def __agent(self, cmd):  # 私有方法,只有当前类的对象可调用import subprocessoutput = subprocess.getoutput(cmd)return outputdef __ssh(self, cmd):import paramiko# 通过公钥私钥方式登陆远程服务器# private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key)# ssh = paramiko.SSHClient()# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())# ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, pkey=private_key)# stdin, stdout, stderr = ssh.exec_command(cmd)# result = stdout.read()# ssh.close()ssh = paramiko.SSHClient()ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, password=self.ssh_pwd)stdin, stdout, stderr = ssh.exec_command(cmd)result = stdout.read()ssh.close()return result#   通过salt方式获得远程服务器信息def __salt(self, cmd):salt_cmd = "salt '%s' cmd.run '%s'" % (self.hostname, cmd,)import subprocessoutput = subprocess.getoutput(salt_cmd)return output

basic.py采集基础信息:

class Basic:"""获取服务器基本信息(服务器名称...)"""def __init__(self):pass@classmethoddef inital(cls):  # 定义类方法,用于扩展,在执行init方法时提前执行的扩展方法return cls()def process(self, command_func, debug):if debug:  # 用于在windows环境下的测试output = {'os_platform': "linux",'os_version': "CentOS release 6.6 (Final)\nKernel \r on an \m",'hostname': 'c1.com'}else:  # 在linux系统下执行的命令output = {'os_platform': command_func("uname").strip(),  # 采集系统名称'os_version': command_func("cat /etc/issue").strip().split('\n')[0],  # 采集系统版本'hostname': command_func("hostname").strip(),  # 采集系统版本名称}return output

board.py采集主板信息:

from lib.conf.config import settings
import osclass Board:"""获取服务器主板信息"""def __init__(self):pass@classmethoddef initial(cls):return cls()def process(self, command_func, debug):if debug:output = open(os.path.join(settings.BASEDIR, 'files/board.out'), 'r', encoding='utf-8').read()else:output = command_func('sudo dmidecode -t1')  # 调用command_func方法来执行对应的采集方式return self.parse(output)# 解析服务器信息结果def parse(self, content):result = {}key_map = {'Manufacturer': 'manufacturer','Product Name': 'model','Serial Number': 'sn',}for item in content.split('\n'):row_data = item.strip().split(':')if len(row_data) == 2:if row_data[0] in key_map:result[key_map[row_data[0]]] = row_data[1].strip() if row_data[1] else row_data[1]return result

cpu.py 采集CPU信息:

import os
from lib.conf.config import settingsclass Cpu:"""获取服务器CPUT信息"""def __init__(self):pass@classmethoddef initial(cls):return cls()def process(self, command_func, debug):if debug:output = open(os.path.join(settings.BASEDIR, 'files/cpuinfo.out'), 'r', encoding='utf-8').read()else:output = command_func("cat /proc/cpuinfo")return self.parse(output)def parse(self, content):"""解析shell命令返回结果:param content: shell 命令结果:return:解析后的结果"""response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''}cpu_physical_set = set()content = content.strip()for item in content.split('\n\n'):for row_line in item.split('\n'):key, value = row_line.split(':')key = key.strip()if key == 'processor':response['cpu_count'] += 1elif key == 'physical id':cpu_physical_set.add(value)elif key == 'model name':if not response['cpu_model']:response['cpu_model'] = valueresponse['cpu_physical_count'] = len(cpu_physical_set)return response

disk.py 采集硬盘信息:

import re
import os
from lib.conf.config import settingsclass Disk:"""获取服务器硬盘信息"""def __init__(self):pass@classmethoddef initial(cls):return cls()def process(self, command_func, debug):if debug:output = open(os.path.join(settings.BASEDIR, 'files/disk.out'), 'r', encoding='utf-8').read()else:output = command_func("sudo MegaCli  -PDList -aALL")return self.parse(output)def parse(self, content):"""解析shell命令返回结果:param content: shell 命令结果:return:解析后的结果"""response = {}result = []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():continueif len(row.split(':')) != 2:continuekey, value = row.split(':')name = self.mega_patter_match(key)if name:if key == 'Raw Size':raw_size = re.search('(\d+\.\d+)', value.strip())if raw_size:temp_dict[name] = raw_size.group()else:raw_size = '0'else:temp_dict[name] = value.strip()if temp_dict:response[temp_dict['slot']] = temp_dictreturn response@staticmethoddef mega_patter_match(needle):grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}for key, value in grep_pattern.items():if needle.startswith(key):return valuereturn False

memory.py 采集内存信息:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
from lib import convert
from lib.conf.config import settingsclass Memory(object):"""获取服务器内存信息"""def __init__(self):pass@classmethoddef initial(cls):return cls()def process(self, command_func, debug):if debug:output = open(os.path.join(settings.BASEDIR, 'files/memory.out'), 'r', encoding='utf-8').read()else:output = command_func("sudo dmidecode  -q -t 17 2>/dev/null")return self.parse(output)def parse(self, content):"""解析shell命令返回结果:param content: shell 命令结果:return:解析后的结果"""ram_dict = {}key_map = {'Size': 'capacity','Locator': 'slot','Type': 'model','Speed': 'speed','Manufacturer': 'manufacturer','Serial Number': 'sn',}devices = content.split('Memory Device')for item in devices:item = item.strip()if not item:continueif item.startswith('#'):continuesegment = {}lines = item.split('\n\t')for line in lines:if not line.strip():continueif len(line.split(':')):key, value = line.split(':')else:key = line.split(':')[0]value = ""if key in key_map:if key == 'Size':segment[key_map['Size']] = convert.convert_mb_to_gb(value, 0)else:segment[key_map[key.strip()]] = value.strip()ram_dict[segment['slot']] = segmentreturn ram_dict

采集网卡信息:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import re
from lib.conf.config import settingsclass Nic(object):"""获取服务器网卡信息"""def __init__(self):pass@classmethoddef initial(cls):return cls()def process(self, command_func, debug):if debug:output = open(os.path.join(settings.BASEDIR, 'files/nic.out'), 'r', encoding='utf-8').read()interfaces_info = self._interfaces_ip(output)else:interfaces_info = self.linux_interfaces(command_func)self.standard(interfaces_info)return interfaces_infodef linux_interfaces(self, command_func):'''Obtain interface information for *NIX/BSD variants'''ifaces = dict()ip_path = 'ip'if ip_path:cmd1 = command_func('sudo {0} link show'.format(ip_path))cmd2 = command_func('sudo {0} addr show'.format(ip_path))ifaces = self._interfaces_ip(cmd1 + '\n' + cmd2)return ifacesdef which(self, exe):def _is_executable_file_or_link(exe):# check for os.X_OK doesn't suffice because directory may executablereturn (os.access(exe, os.X_OK) and(os.path.isfile(exe) or os.path.islink(exe)))if exe:if _is_executable_file_or_link(exe):# executable in cwd or fullpathreturn exe# default path based on busybox's defaultdefault_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin'search_path = os.environ.get('PATH', default_path)path_ext = os.environ.get('PATHEXT', '.EXE')ext_list = path_ext.split(';')search_path = search_path.split(os.pathsep)if True:# Add any dirs in the default_path which are not in search_path. If# there was no PATH variable found in os.environ, then this will be# a no-op. This ensures that all dirs in the default_path are# searched, which lets salt.utils.which() work well when invoked by# salt-call running from cron (which, depending on platform, may# have a severely limited PATH).search_path.extend([x for x in default_path.split(os.pathsep)if x not in search_path])for path in search_path:full_path = os.path.join(path, exe)if _is_executable_file_or_link(full_path):return full_pathreturn Nonedef _number_of_set_bits_to_ipv4_netmask(self, set_bits):  # pylint: disable=C0103'''Returns an IPv4 netmask from the integer representation of that mask.Ex. 0xffffff00 -> '255.255.255.0''''return self.cidr_to_ipv4_netmask(self._number_of_set_bits(set_bits))def cidr_to_ipv4_netmask(self, cidr_bits):'''Returns an IPv4 netmask'''try:cidr_bits = int(cidr_bits)if not 1 <= cidr_bits <= 32:return ''except ValueError:return ''netmask = ''for idx in range(4):if idx:netmask += '.'if cidr_bits >= 8:netmask += '255'cidr_bits -= 8else:netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits)))cidr_bits = 0return netmaskdef _number_of_set_bits(self, x):'''Returns the number of bits that are set in a 32bit int'''# Taken from http://stackoverflow.com/a/4912729. Many thanks!x -= (x >> 1) & 0x55555555x = ((x >> 2) & 0x33333333) + (x & 0x33333333)x = ((x >> 4) + x) & 0x0f0f0f0fx += x >> 8x += x >> 16return x & 0x0000003fdef _interfaces_ip(self, out):'''Uses ip to return a dictionary of interfaces with various information abouteach (up/down state, ip address, netmask, and hwaddr)'''ret = dict()right_keys = ['name', 'hwaddr', 'up', 'netmask', 'ipaddrs']def parse_network(value, cols):'''Return a tuple of ip, netmask, broadcastbased on the current set of cols'''brd = Noneif '/' in value:  # we have a CIDR in this addressip, cidr = value.split('/')  # pylint: disable=C0103else:ip = value  # pylint: disable=C0103cidr = 32if type_ == 'inet':mask = self.cidr_to_ipv4_netmask(int(cidr))if 'brd' in cols:brd = cols[cols.index('brd') + 1]return (ip, mask, brd)groups = re.compile('\r?\n\\d').split(out)for group in groups:iface = Nonedata = dict()for line in group.splitlines():if ' ' not in line:continuematch = re.match(r'^\d*:\s+([\w.\-]+)(?:@)?([\w.\-]+)?:\s+<(.+)>', line)if match:iface, parent, attrs = match.groups()if 'UP' in attrs.split(','):data['up'] = Trueelse:data['up'] = Falseif parent and parent in right_keys:data[parent] = parentcontinuecols = line.split()if len(cols) >= 2:type_, value = tuple(cols[0:2])iflabel = cols[-1:][0]if type_ in ('inet',):if 'secondary' not in cols:ipaddr, netmask, broadcast = parse_network(value, cols)if type_ == 'inet':if 'inet' not in data:data['inet'] = list()addr_obj = dict()addr_obj['address'] = ipaddraddr_obj['netmask'] = netmaskaddr_obj['broadcast'] = broadcastdata['inet'].append(addr_obj)else:if 'secondary' not in data:data['secondary'] = list()ip_, mask, brd = parse_network(value, cols)data['secondary'].append({'type': type_,'address': ip_,'netmask': mask,'broadcast': brd,})del ip_, mask, brdelif type_.startswith('link'):data['hwaddr'] = valueif iface:if iface.startswith('pan') or iface.startswith('lo') or iface.startswith('v'):del iface, dataelse:ret[iface] = datadel iface, datareturn retdef standard(self, interfaces_info):for key, value in interfaces_info.items():ipaddrs = set()netmask = set()if not 'inet' in value:value['ipaddrs'] = ''value['netmask'] = ''else:for item in value['inet']:ipaddrs.add(item['address'])netmask.add(item['netmask'])value['ipaddrs'] = '/'.join(ipaddrs)value['netmask'] = '/'.join(netmask)del value['inet']

四、资产入库数据库表设计


from django.db import modelsclass 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.nameclass AdminInfo(models.Model):"""用户登陆相关信息"""user_info = models.OneToOneField("UserProfile", on_delete='')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.nameclass 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.nameclass BusinessUnit(models.Model):"""业务线"""name = models.CharField('业务线', max_length=64, unique=True)contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c', on_delete='')manager = models.ForeignKey('UserGroup', verbose_name='系统管理员', related_name='m', on_delete='')class Meta:verbose_name_plural = "业务线表"def __str__(self):return self.nameclass IDC(models.Model):"""机房信息"""name = models.CharField('机房', max_length=32)floor = models.IntegerField('楼层', default=1)class Meta:verbose_name_plural = "机房表"def __str__(self):return self.nameclass Tag(models.Model):"""资产标签"""name = models.CharField('标签', max_length=32, unique=True)class Meta:verbose_name_plural = "标签表"def __str__(self):return self.nameclass 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, on_delete='')business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True, on_delete='')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', on_delete='')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) # ip地址字段,有校验功能manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True) # ip地址字段,有校验功能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.hostnameclass NetworkDevice(models.Model):asset = models.OneToOneField('Asset', on_delete='')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', on_delete='')class Meta:verbose_name_plural = "硬盘表"def __str__(self):return self.slotclass 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', on_delete='')class Meta:verbose_name_plural = "网卡表"def __str__(self):return self.nameclass 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', on_delete='')class Meta:verbose_name_plural = "内存表"def __str__(self):return self.slotclass AssetRecord(models.Model):"""资产变更记录,creator为空时,表示是资产汇报的数据。"""asset_obj = models.ForeignKey('Asset', related_name='ar', on_delete='')content = models.TextField(null=True)# 新增硬盘creator = models.ForeignKey('UserProfile', null=True, blank=True, on_delete='') #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, on_delete='')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

五、API获取资产并保存入库


urls:

url(r'api/', include('api.urls')),

api模块:

url(r'^asset.html$', views.asset),

views:

def asset(request):if request.method == 'POST':# 新资产信息server_info = json.loads(request.body.decode('utf-8')) # 将发送的数据解码成字符串,再通过json反序列化成字典格式hostname = server_info['basic']['data']['hostname'] # 获得采集器中发过来的信息中的主机名server_obj = models.Server.objects.filter(hostname = hostname)if not server_obj:return HttpResponse('当前主机名在资产中未录入')return HttpResponse('')

七、资产入库处理 (以硬盘为例)


views:

from django.shortcuts import render
import json
from django.shortcuts import HttpResponse
from repository import models# Create your views here.
def asset(request):if request.method == 'POST':# 新资产信息server_info = json.loads(request.body.decode('utf-8'))  # 将发送的数据解码成字符串,再通过json反序列化成字典格式hostname = server_info['basic']['data']['hostname']  # 获得采集器中发过来的信息中的主机名server_obj = models.Server.objects.filter(hostname=hostname).first()  # 根据主机名获得服务器QuuerySet对象if not server_obj:return HttpResponse('当前主机名在资产中未录入')# 将硬盘信息入库# 'disk': {'stauts': True, 'data': {#     '0': {'slot': '0', 'pd_type': 'SAS', 'capacity': '279.396',#           'model': 'SEAGATE ST300MM0006     LS08S0K2B5NV'},#     '1': {'slot': '1', 'pd_type': 'SAS', 'capacity': '279.396',#           'model': 'SEAGATE ST300MM0006     LS08S0K2B5AH'},#     '2': {'slot': '2', 'pd_type': 'SATA', 'capacity': '476.939',#           'model': 'S1SZNSAFA01085L     Samsung SSD 850 PRO 512GB               EXM01B6Q'},#     '3': {'slot': '3', 'pd_type': 'SATA', 'capacity': '476.939',#           'model': 'S1AXNSAF912433K     Samsung SSD 840 PRO Series              DXM06B0Q'},#     '4': {'slot': '4', 'pd_type': 'SATA', 'capacity': '476.939',#           'model': 'S1AXNSAF303909M     Samsung SSD 840 PRO Series              DXM05B0Q'},#     '5': {'slot': '5', 'pd_type': 'SATA', 'capacity': '476.939',#           'model': 'S1AXNSAFB00549A     Samsung SSD 840 PRO Series              DXM06B0Q'}}},if not server_info['disk']['stauts']:  # 采集信息中状态为False时,将错误信息添加到错误日志中models.ErrorLog.objects.create(content=server_info['disk']['data'], asset_obj=server_obj.asset,title='【%s】硬盘采集错误信息' % hostname)new_disk_dict = server_info['disk']['data']  # 获得服务器中最新的硬盘信息数据"""{5: {'slot':5,capacity:476...}3: {'slot':3,capacity:476...}}"""old_disk_list = models.Disk.objects.filter(server_obj=server_obj)  # 获得服务器中之前的所有硬盘数据QuerySer对象列表"""[Disk('slot':5,capacity:476...)Disk('slot':4,capacity:476...)]"""new_slot_list = list(new_disk_dict.keys())  # 获得最新硬盘数据中的插槽ID ,[0,1,2,3,4,5]old_slot_list = []  # 获得之前的硬盘数据中的插槽IDfor item in old_disk_list:old_slot_list.append(item.slot)# 采用交集运算后的结果作为硬盘数据的 更新,获得共有的数据进行比较update_list = set(new_slot_list).intersection(old_slot_list)  # 采用集合进行交集运算#  采用差集运算后的结果作为硬盘数据的 创建(新数据有,老数据没有)create_list = set(new_slot_list).difference(old_slot_list)# 采用差集运算后的结果作为硬盘数据的 删除(老数据有,新数据没有)del_list = set(old_slot_list).difference(new_slot_list)###################从硬盘数据表中删除数据#################3if del_list:models.Disk.objects.filter(server_obj=server_obj, slot__in=del_list).delete()# 记录日志信息models.AssetRecord.objects.create(asset_obj=server_obj.asset, content='移除硬盘:%s' % ('、'.join(del_list)))###################从硬盘数据表中增加数据#################record_list = []for slot in create_list:disk_dict = new_disk_dict[slot]  # {'capacity': '476.939', 'slot': '4', 'model': 'S1AXNSAF303909M     Samsung SSD 840 PRO Seriesdisk_dict['server_obj'] = server_obj  # 同时将服务器对象添加到该字典中一同添加到Disk数据表中models.Disk.objects.create(**disk_dict)  # 以字典的形式添加数据到Disk数据表中# 组装硬盘变更记录信息temp = "新增硬盘:位置{slot},容量{capacity},型号:{model},类型:{pd_type}".format(**disk_dict)record_list.append(temp)if record_list:content = ';'.join(record_list)  # 将所有变更信息拼接成一个字符串models.AssetRecord.objects.create(asset_obj=server_obj.asset, content=content)  # 将变更记录添加到记录表中###################从硬盘数据表中修改数据#################record_list = []  # 变更记录列表row_map = {'capacity': '容量', 'pd_type': '类型', 'model': '型号'}for slot in update_list:new_disk_row = new_disk_dict[slot]  # 获得新采集过来的单条硬盘数据old_disk_row = models.Disk.objects.filter(slot=slot, server_obj=server_obj).first()for k, v in new_disk_row.items():# k: capacity;  slot;   pd_type;    model# v: '476.939'  'xxies              DXM05B0Q'   'SATA'value = getattr(old_disk_row, k)  # 通过反射获得对象中属性的值if v != value:  # 如果两者中的值不相等则表示需要更新setattr(old_disk_row, k, v)  # 将对象中的属性值重新赋值record_list.append('槽位%s,%s由%s变更为%s' % (slot, row_map[k], value, v))old_disk_row.save()  # 保存更新后的硬盘数据# 将变更信息保存到变更记录表中if record_list:content = ";".join(record_list)models.AssetRecord.objects.create(asset_obj=server_obj.asset, content=content)return HttpResponse('')

八、牛x的API验证


采集程序客户端的动态令牌生成:

############### 客户端生成并发送动态令牌完成API验证 ###############
import requests
import time
import hashlib# 生成动态令牌
ctime = time.time()  # 动态时间戳
key = "asdfasdfasdfasdf098712sdfs"  # 假定API发送过来的静态令牌
new_key = '%s|%s' % (key, ctime)  # 在静态令牌基础加入动态时间,形成动态令牌
print(ctime)# 动态令牌通过md5进行加密
m = hashlib.md5()  # 初始化md5
m.update(bytes(new_key, encoding='utf-8'))  # 将动态令牌转换成字节,将由md5进行计算
md5_key = m.hexdigest()  # 获得加密后的令牌
print(md5_key)md5_time_key = '%s|%s' % (md5_key, ctime)  # 将生成动态令牌所需的时间一同发给API,让API进行md5进行加密完成动态令牌的生成,以便完成动态令牌数据的比对# 将添加了时间的动态令牌添加到请求头中发往API
response = requests.get('http://127.0.0.1:8000/api/asset.html', headers={'OpenKey': md5_time_key})
print(response.text)  # 获得响应结果

API接收令牌完成身份验证:

from django.shortcuts import render
import json
from django.shortcuts import HttpResponse
from repository import models
import time
from server import settings
import hashlibapi_key_record = {  # 访问记录表,由动态令牌作为key, 生成动态令牌中所需的时间+10s 作为value,表示该值保存10s# "1b96b89695f52ec9de8292a5a7945e38|1501472467.4977243":1501472477.4977243
}def asset(request):client_md5_time_key = request.META.get('HTTP_OPENKEY')  # 获取客户端通过请求头中发送过来的数据client_md5_key, client_ctime = client_md5_time_key.split('|')  # 切分出动态令牌和时间client_ctime = float(client_ctime)server_time = time.time()  # 获得服务器当前时间# 第一关验证:客户端第二次发送动态令牌的时间不能超过10s,完成第一层黑客攻击过滤if server_time - client_ctime > 10:return HttpResponse('第一关通过失败,时间超时')# 第二关验证:生成动态令牌的时间不匹配,防止黑客获得动态令牌,并通过第一关到达第二关temp = '%s|%s' % (settings.AUTH_KEY, client_ctime)   # 从配置文件中读取出静态令牌# 完成服务端的动态令牌生成m = hashlib.md5()m.update(bytes(temp, encoding='utf-8'))server_md5_key = m.hexdigest()if server_md5_key != client_md5_key:   # 如果两个动态令牌不相等return HttpResponse('第二关通过失败,生成服务端动态令牌中的时间与生成动态令牌中的时间不一致')# 对api_key_record记录表中进行数据更新,用于第三关验证for k in list(api_key_record.keys()):v = api_key_record[k]if server_time > v:  # 如果服务器当前时间大于动态令牌有效时间,则删除该令牌,以便减少记录表容量占比del api_key_record[k]# 第三关:保持记录表中的唯一性,如果发送过请求,则其它的请求无效if client_md5_time_key in api_key_record:return HttpResponse('第三关通过失败,已经有人访问过了')else:api_key_record[client_md5_time_key] = client_ctime + 10  # 将是第一次的请求写入记录表中if request.method == 'GET':ys = 'api验证成功'return HttpResponse(ys)elif request.method == 'POST':略……

九、对资产信息进行AES加密


client客户端程序utils.py:

from lib.conf.config import settings
from Crypto.Cipher import AESdef encrypt(message):"""AES加密资产数据:param message::return:"""key = settings.DATA_KEYcipher = AES.new(key, AES.MODE_CBC, key)  # 初始化AES对象ba_data = bytearray(message, encoding='utf-8')  # 字符串编码成字节数组v1 = len(ba_data)  # 计算需要加密的数据的长度v2 = v1 % 16  # AES只对16的倍数的字节长度进行加密if v2 == 0:v3 = 16else:v3 = 16 - v2for i in range(v3):  # 需要为不是16倍数的字节数组添加字节,只至满足是16的倍数ba_data.append(v3)  # 添加的字节内容采用需补齐的长度值final_data = ba_data.decode('utf-8')  # 字节解码成字符串msg = cipher.encrypt(final_data)  # 对字符串进行加密,成为字节return msgdef decrypt(msg):"""数据解密:param msg::return:"""from Crypto.Cipher import AESkey = settings.DATA_KEYcipher = AES.new(key, AES.MODE_CBC, key)result = cipher.decrypt(msg)data = result[0:-result[-1]]  # 获取补齐到16位长度前真正的内容,result[-1]表示补齐的长度值return str(data, encoding='utf-8')  # 转换成字符串def auth():"""API验证服务器身份:return:"""import timeimport hashlib# 生成动态令牌ctime = time.time()  # 动态时间戳key = "asdfasdfasdfasdf098712sdfs"  # 假定API发送过来的令牌new_key = '%s|%s' % (key, ctime)  # 在原有的随机字符串上加入动态时间,形成动态令牌print(ctime)# 动态令牌通过md5进行加密m = hashlib.md5()  # 初始化md5m.update(bytes(new_key, encoding='utf-8'))  # 将动态令牌转换成字节,将由md5进行计算md5_key = m.hexdigest()  # 获得加密后的令牌print(md5_key)md5_time_key = '%s|%s' % (md5_key, ctime)  # 将生成动态令牌所需的时间一同发给API,让API进行md5进行加密,以便完成加密数据的比对return md5_time_key

client.py :

class Base(object):"""负责往api发送数据"""def post_asset(self, server_info):# 将数据转换成json字符串格式发送data = encrypt(json.dumps(server_info))    # 将字典格式的数据转换成encrypt所需的字符串格式,然后加密response = requests.post(url=settings.API,data = data,headers={'OpenKey':auth(), 'Content-Type':'application/json'}   # )print(response.text)

server服务端程序中的api模块接收数据:

def decrypt(msg):"""对AES加密数据进行解密:param msg: :return: """from Crypto.Cipher import AESkey = b'dfdsdfsasdfdsdfs'   # 该Key值需与客户端加密所需的Key保持相同cipher = AES.new(key, AES.MODE_CBC, key)result = cipher.decrypt(msg)  # result = b'\xe8\xa6\x81\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0sdfsd\t\t\t\t\t\t\t\t\t'data = result[0:-result[-1]]return str(data, encoding='utf-8')def asset(request):"""接收客户端采集的资产信息:param request: :return: """if ……elif request.method == 'POST':server_info = decrypt(request.body) # 对客户端发送过来的AES加密数据进行解密server_info = json.loads(server_info)   # 对字符串进行反序列化成字典print(server_info)

十、CMDB后台管理之CURD插件数据格式化


urls:

    url(r'^curd.html$', views.curd), # 进入到数据展示页面

views:

def curd(request):"""进入到curd.html页面:param request::return:"""return render(request, 'curd.html')

html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css" />
</head>
<body>
<div style="width: 700px; margin: 0 auto"><table class="table table-bordered table-striped"><thead id="tbHead"><tr></tr></thead><tbody id="tbBody"></tbody></table></div><script src="/static/jquery-1.12.4.js"></script>

urls:

    url(r'^curd_json.html$', views.curd_json)   # 通过页面加载时启动的js获得数据呈现在页面中

views:


def curd_json(request):"""进行数据结构处理:param request::return:"""table_config = [  # 配置文件,用于前端页面数据定制显示{'q': 'id',  # 用于数据库查询字段名'title': 'ID',  # 用于前端页面中表头字段名的显示'text': {'tpl': '{n1}',  # 用于生成格式化字符串中的占位符'kwargs': {'n1': '@id'}  # 占位符中具体的id数值,用于生成链接中对单条数据的操作}},{'q': 'hostname','title': '主机名','text': {'tpl': '{n1}-{n2}','kwargs': {'n1': '@hostname', 'n2': '@id'}}},{'q': 'create_at','title': '创建时间','text': {'tpl': '{n1}','kwargs': {'n1': '@create_at'}}},{'q': 'asset__cabinet_num', 'title': '机柜号','text': {'tpl': "BJ-{n1}",'kwargs': {'n1': '@asset__cabinet_num'}}},{'q': 'asset__business_unit__name','title': '业务线名称','text': {'tpl': "{n1}",'kwargs': {'n1': '@asset__business_unit__name'}}},# 页面显示 操作: 删除,编辑,a标签生成{'q':None,'title':'操作','text':{'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs':{'nid':'@id'},}},]# 组装数据库查询所需的字段value_list = []for row in table_config:if not row['q']:continuevalue_list.append(row['q'])from datetime import datetimefrom datetime import dateclass JsonCustomEncoder(json.JSONEncoder):"""json扩展:针对日期格式数据进行自定义转换成字符串处理"""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)    # 调用父类中的default方法server_list = models.Server.objects.values(*value_list)   # 传入列表获得字典格式数据ret = {'server_list':list(server_list),   # 将Querylist转换成列表'table_config':table_config,}return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

html:

<script src="/static/jquery-1.12.4.js"></script>
<script>$(function () {{#通过ajax异步获得初始化数据#}initial();});// 为字符串创建format方法,用于字符串格式化String.prototype.format = function (args) {return this.replace(/\{(\w+)\}/g, function (s, i) {return args[i];});};{#页面加载时发送ajax请求#}function initial() {$.ajax({url: '/backend/curd_json.html',type: 'GET',{#将响应的字符串数据转换成字典格式#}dataType: 'JSON',success: function (arg) {{#生成表头字段#}initTableHeader(arg.table_config);{#生成表格数据#}initTableBody(arg.server_list, arg.table_config);}})}// {#生成表头字段#}function initTableHeader(tableConfig) {$('#tbHead').empty()    // 清除该标签内的所有内容var tr = document.createElement('tr')    // 生成tr标签// {#循环生成字段表头#}$.each(tableConfig, function (k, v) {if (v.display) { // 为Ture时需要展示var tag = document.createElement('th');tag.innerHTML = v.title$(tr).append(tag);}})$('#tbHead').append(tr);}{#生成表格数据信息#}function initTableBody(serverList, tableConfig) {$.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')$.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"var td = document.createElement('td');// rrow['q']// rrow['text']// rrow.text.tpl = "asdf{n1}sdf"// rrow.text.kwargs = {'n1':'@id','n2':'123'}var newKwargs = {}; // {'n1':'1','n2':'123'}$.each(rrow.text.kwargs,function(kkk,vvv){var av = vvv;{#@表示需要进行字符串格式化#}if (vvv[0] == '@') {{#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}av = row[vvv.substring(1, vvv.length)];}newKwargs[kkk] = av;});{#通过自定义的扩展方法进行字符串格式化#}var newText = rrow.text.tpl.format(newKwargs);td.innerHTML = newText;$(tr).append(td)});$('#tbBody').append(tr);})}</script>

十一、CMDB后台管理之封装基本插件


views:

# 略……
def curd_json(request):"""进行数据结构处理:param request::return:"""# q表示数据库查询字段,# title表示前端表格中的表头字段,# text用来将数据库中取出的值进行字符串格式化# display表示该字段在前端页面表格表头是否显示table_config = [  # 配置文件,用于前端页面数据定制显示{'q': 'id',  # 用于数据库查询字段名'title': 'ID',  # 用于前端页面中表头字段名的显示'display':False,'text': {'tpl': '{n1}',  # 用于生成格式化字符串中的占位符'kwargs': {'n1': '@id'}  # 占位符中具体的id数值,用于生成链接中对单条数据的操作}},{'q': 'hostname','title': '主机名','display': True,'text': {'tpl': '{n1}-{n2}','kwargs': {'n1': '@hostname', 'n2': '@id'}}},# 页面显示 操作: 删除,编辑,a标签生成{'q':None,'title':'操作','display': True,'text':{'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs':{'nid':'@id'},}},]# 略……

nb-list.js自定义Js函数插件:

// 自定义js匿名函数,属于自动调用,只有内部调用,防止当成插件时的同名冲突
(function (jq) {// 为字符串创建format方法,用于字符串格式化String.prototype.format = function (args) {return this.replace(/\{(\w+)\}/g, function (s, i) {return args[i];});};// {#页面加载时发送ajax请求#}function initial(url) {$.ajax({url: url,type: 'GET',// {#将响应的字符串数据转换成字典格式#}dataType: 'JSON',success: function (arg) {// {#生成表头字段#}initTableHeader(arg.table_config);// {#生成表格数据#}initTableBody(arg.server_list, arg.table_config);}})}// {#生成表头字段#}function initTableHeader(tableConfig) {$('#tbHead').empty()    // 清除该标签内的所有内容var tr = document.createElement('tr')    // 生成tr标签// {#循环生成字段表头#}$.each(tableConfig, function (k, v) {if (v.display) { // 为Ture时需要展示var tag = document.createElement('th');tag.innerHTML = v.title$(tr).append(tag);}})$('#tbHead').append(tr);}// {#生成表格数据信息#}function initTableBody(serverList, tableConfig) {$.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')$.each(tableConfig, function (kk, rrow) {if (rrow.display) {      // 是否需要展示该字段对应的内容// 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"var td = document.createElement('td');// rrow['q']// rrow['text']// rrow.text.tpl = "asdf{n1}sdf"// rrow.text.kwargs = {'n1':'@id','n2':'123'}var newKwargs = {}; // {'n1':'1','n2':'123'}$.each(rrow.text.kwargs, function (kkk, vvv) {var av = vvv;// {#@表示需要进行字符串格式化#}if (vvv[0] == '@') {// {#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}av = row[vvv.substring(1, vvv.length)];}newKwargs[kkk] = av;});// {#通过自定义的扩展方法进行字符串格式化#}var newText = rrow.text.tpl.format(newKwargs);td.innerHTML = newText;$(tr).append(td)}});$('#tbBody').append(tr);})}jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用xx: function (url) {// {#通过ajax异步请求获得初始化数据#}initial(url)}})
})(jQuery)  // 传入jQeury对象

html:

# 略……
<script src = "/static/nb-list.js"></script>
<script>{#调用自定义的jQuery函数#}$.xx('/backend/curd_json.html');
</script>
# 略……

十二、CMDB后台管理之CURD插件显示cho(数字对应的字符串)


urls:

    # 资产信息展示url(r'^asset.html$', views.asset),url(r'^asset_json.html$', views.asset_json),

views:

# 资产信息展示
def asset(request):"""跳转到资产信息展示页面:param request::return:"""return render(request, 'asset.html',)def asset_json(reqeust):"""ajax获取资产信息数据:param reqeust::return:"""# @代表需要字符串格式化,@@用于将数字结果转换成对应字符串展示table_config = [    # 配置文件{'q':'id','title':'ID','display':False,'text':{'tpl': "{n1}",'kwargs': {'n1': '@id'}},},{'q': 'device_type_id','title': '资产类型','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@@device_type_choices'}}},{'q': 'device_status_id','title': '状态','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@@device_status_choices'}}},{'q': 'cabinet_num','title': '机柜号','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@cabinet_num'}}},{'q': 'idc__name','title': '机房','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@idc__name'}}},# 页面显示:标题:操作;删除,编辑:a标签{'q': None,'title': '操作','display': True,'text': {'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs': {'nid': '@id'}}},]# 用于数据库查询字段value_list = []for row in table_config:if not row['q']:continuevalue_list.append(row['q'])server_list = models.Asset.objects.values(*value_list)# global_dict用于生成选择元组中的数字对应的字符串ret={'server_list': list(server_list),'table_config':table_config,'global_dict':{'device_type_choices':models.Asset.device_type_choices,'device_status_choices':models.Asset.device_status_choices,}}return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title></title><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css" />
</head>
<body><div style="width: 700px;margin: 0 auto"><h1>资产列表</h1><table class="table table-bordered table-striped"><thead id="tbHead"><tr></tr></thead><tbody id="tbBody"></tbody></table></div><script src="/static/jquery-1.12.4.js"></script><script src="/static/nb-list.js"></script><script>$.xx('/backend/asset_json.html');</script>
</body>
</html>

nb-list.js自定义js文件:

/*** Created by Administrator on 2017/8/2.*/
(function (jq) {// 全局常量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 initial(url) {$.ajax({url: url,type: 'GET',  // 获取数据dataType: 'JSON',success: function (arg) {$.each(arg.global_dict,function(k,v){GLOBAL_DICT[k] = v});/*{'server_list':list(server_list), # 所有数据'table_config':table_config      # 所有配置'global_dict':{'device_type_choices': ((1, '服务器'),(2, '交换机'),(3, '防火墙'),)'device_status_choices': ((1, '上架'),(2, '在线'),(3, '离线'),(4, '下架'),)}}*/initTableHeader(arg.table_config);initTableBody(arg.server_list, arg.table_config);}})}// {#生成表头字段#}function initTableHeader(tableConfig) {$('#tbHead').empty()    // 清除该标签内的所有内容var tr = document.createElement('tr')    // 生成tr标签// {#循环生成字段表头#}$.each(tableConfig, function (k, v) {if (v.display) { // 为Ture时需要展示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-},]*/$.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');$.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');/*if(rrow['q']){td.innerHTML = row[rrow.q];}else{td.innerHTML = rrow.text;}*/// rrow['q']// rrow['text']// rrow.text.tpl = "asdf{n1}sdf"// rrow.text.kwargs = {'n1':'@id','n2':'@@123'}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];console.log(nid,global_dict_key); // 1 "device_type_choices"$.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;$(tr).append(td);}});$('#tbBody').append(tr);})}jq.extend({xx: function (url) {initial(url);}})
})(jQuery);

十三、CMDB后台管理之CURD插件定制属性


views:

def asset_json(reqeust):"""ajax获取资产信息数据:param reqeust::return:"""# @代表需要字符串格式化,@@用于将数字结果转换成对应字符串展示table_config = [    # 配置文件{'q':'id','title':'ID','display':False,'text':{'tpl': "{n1}",'kwargs': {'n1': '@id'}},'attrs':{'k1':'v1', 'k2':'@id'}},{'q': 'device_type_id','title': '资产类型','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@@device_type_choices'}},'attrs': {'k1': 'v1', 'k2': '@id'}},{'q': 'device_status_id','title': '状态','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@@device_status_choices'}},'attrs': {'k1': 'v1', 'k2': '@id'}},{'q': 'cabinet_num','title': '机柜号','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@cabinet_num'}},'attrs': {'k1': 'v1', 'k2': '@id'}},{'q': 'idc__name','title': '机房','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@idc__name'}},'attrs': {'k1': 'v1', 'k2': '@id'}},# 页面显示:标题:操作;删除,编辑:a标签{'q': None,'title': '操作','display': True,'text': {'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs': {'nid': '@id'}},'attrs': {'k1': 'v1', 'k2': '@id'}},]
# 略……

nb-list.js自定义js扩展文件:

# 略……// 在标签中添加属性$.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)# 略……               

十四、增删改查插件之当前行进入编辑模式


views:

from django.shortcuts import render
import json
from repository import models
from django.shortcuts import HttpResponse
from datetime import datetime
from datetime import date
# Create your views here.
class JsonCustomEncoder(json.JSONEncoder):"""json扩展:针对日期格式数据进行自定义转换成字符串处理"""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)  # 调用父类中的default方法def curd(request):"""进入到curd.html页面:param request::return:"""return render(request, 'curd.html')def curd_json(request):"""ajax请求方法:param request::return:"""table_config = [  # 配置文件,用于前端页面数据定制显示# 生成checkbox多选框字段{'q': None,  # 不作为数据库查询字段'title': '选择','display': True,'text': {'tpl': "<input type='checkbox' value='{n1}' />",'kwargs': {'n1': '@id',}},'attrs': {'nid': '@id'}},# 生成id字段{'q': 'id',  # 用于数据库查询字段名'title': 'ID',  # 用于前端页面中表头字段名的显示'display':False,# display表示该字段在前端页面表格表头是否显示'text': { # text用来将数据库中取出的值进行字符串格式化'tpl': '{n1}',  # 用于生成格式化字符串中的占位符模板'kwargs': {'n1': '@id'}  # 占位符中具体的id数值,用于生成链接中对单条数据的操作},'attrs':{'k1':'v1','k2':'@hostname'}   # 为前端标签添加属性及属性值},{'q': 'hostname','title': '主机名','display': True,'text': {'tpl': '{n1}-{n2}','kwargs': {'n1': '@hostname', 'n2': '@id'}},'attrs':{'edit-enable':'true', 'k2':'@hostname'}    # edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改},# 页面显示 操作: 删除,编辑,a标签生成{'q':None,'title':'操作','display': True,'text':{'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs':{'nid':'@id'},},'attrs': {'k1': 'v1', 'k2': '@hostname'}},]# 组装数据库查询所需的字段value_list = []for row in table_config:if not row['q']:continuevalue_list.append(row['q'])server_list = models.Server.objects.values(*value_list)   # 传入列表获得字典格式数据ret = {'server_list':list(server_list),   # 将Querylist转换成列表'table_config':table_config,}return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))# 资产信息展示
def asset(request):"""跳转到资产信息展示页面:param request::return:"""return render(request, 'asset.html',)def asset_json(reqeust):"""ajax获取资产信息数据:param reqeust::return:"""# @代表需要字符串格式化,@@用于将数字结果转换成对应字符串展示table_config = [    # 配置文件# 生成checkbox多选框字段{'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'}},'attrs':{'k1':'v1', 'k2':'@id'}},{'q': 'device_type_id','title': '资产类型','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@@device_type_choices'}},# origin表示数据库字段id对应的值,  global_key表示数据库中下拉框的数据'attrs':{'k1':'v1','origin':'@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':{'edit-enable':'true','origin': '@device_status_id','edit-type':'select','global_key':'device_status_choices' }},{'q': 'cabinet_num','title': '机柜号','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@cabinet_num'}},'attrs': {'k1': 'v1', 'k2': '@id'}},{'q': 'idc__name','title': '机房','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@idc__name'}},'attrs': {'k1': 'v1', 'k2': '@id'}},# 页面显示:标题:操作;删除,编辑:a标签{'q': None,'title': '操作','display': True,'text': {'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs': {'nid': '@id'}},'attrs': {'k1': 'v1', 'k2': '@id'}},]# 用于数据库查询字段value_list = []for row in table_config:if not row['q']:continuevalue_list.append(row['q'])server_list = models.Asset.objects.values(*value_list)# global_dict用于生成选择元组中的数字对应的字符串ret={'server_list': list(server_list),'table_config':table_config,'global_dict':{'device_type_choices':models.Asset.device_type_choices,'device_status_choices':models.Asset.device_status_choices,}}return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

nb-list.js 自定义Js文件:

// 自定义js匿名函数,属于自动调用,只有内部调用,防止当成插件时的同名冲突
(function (jq) {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];});};// {#页面加载时自动发送ajax请求#}function initial(url) {$.ajax({url: url,type: 'GET',// {#将响应的字符串数据转换成字典格式#}dataType: 'JSON',success: function (arg) {// 将 (1, '服务器')……等数据作成全局常量$.each(arg.global_dict, function (k ,v) {GLOBAL_DICT[k] = v});// {#生成表头字段#}initTableHeader(arg.table_config);// {#生成表格数据#}initTableBody(arg.server_list, arg.table_config);}})}// {#生成表头字段#}function initTableHeader(tableConfig) {$('#tbHead').empty()    // 清除该标签内的所有内容var tr = document.createElement('tr')    // 生成tr标签// {#循环生成字段表头#}$.each(tableConfig, function (k, v) {if (v.display) { // 为Ture时需要展示var tag = document.createElement('th');tag.innerHTML = v.title$(tr).append(tag);}})$('#tbHead').append(tr);}// {#生成表格数据信息#}function initTableBody(serverList, tableConfig) {$.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')$.each(tableConfig, function (kk, rrow) {if (rrow.display) {      // 是否需要展示该字段对应的内容// 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"var td = document.createElement('td');// rrow['q']// rrow['text']// rrow.text.tpl = "asdf{n1}sdf"// rrow.text.kwargs = {'n1':'@id','n2':'@@123'}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);  // 获得数据表中的字段名 例device_type_choicesvar nid = row[rrow.q]   // 通过自定义的配置字典,获得数据表中该条数据的id值$.each(GLOBAL_DICT[global_dict_key], function (gk, gv) {if(gv[0] == nid){av = gv[1]; // av = '服务器'}})}// {#@表示需要进行字符串格式化#}else if (vvv[0] == '@') {// {#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}av = row[vvv.substring(1, vvv.length)];}newKwargs[kkk] = av;});// {#通过自定义的扩展方法进行字符串格式化#}var newText = rrow.text.tpl.format(newKwargs);td.innerHTML = newText;// 在标签中添加属性$.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 () {   // 找到tr标签下所有td标签中属性为edit-enable=true的元素,并循环它// $(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');    // 获得当前标签中的origin属性的值$.each(deviceTypeChoices, function (k, v) {  //  v的值为 (1, '服务器'),var option = document.createElement('option');$(option).text(v[1]);  // 为option标签添加文本值$(option).val(v[0]);  // 为option标签添加属性值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) 每一个tdvar editType = $(this).attr('edit-type');   // 获得标签类型if(editType == 'select'){var option = $(this).find('select')[0].selectedOptions;  // 将jquery对象转换为DOM对象,调用selectOptions获得select标签中的options标签$(this).html($(option).text());}else {var inputVal = $(this).find('input').val();    // 获得tr标签中所有input标签的值$(this).html(inputVal);  // 为当前td标签添加html格式内容}})}jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用xx: function (url) {// {#通过ajax异步请求获得初始化数据#}initial(url)// 通过js控制,控制标签类型,完成进入编辑模式功能$('#tbBody').on('click', ':checkbox', function () { // 在tbBody标签范围中为所有checkbox添加click事件// 检测多选框是否已经被选中var $tr = $(this).parent().parent()   // 通过checkbox标签获得tr标签中的元素if ($(this).prop('checked')){   // prop()获得标签属性值// 进入编辑模式trIntoEdit($tr);}else {// 退出编辑模式trOutEdit($tr);}})}})
})(jQuery)  // 传入jQeury对象

十五、增删改查插件之全选反选取消以及进入编辑模式按钮


curd.html

  # 略……
<div style="width: 700px;margin: 0 auto"><h1>服务器列表</h1><div class="btn-group" role="group" aria-label="..."><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="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># 略……

views:

   # 略……// 为所有按钮绑定事件// 为全选按钮绑定事件$('#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 {$(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 () {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);}});}})#略……

十六、增删改查插件之批量删除数据


nb-list.js 自定义文件

// 批量删除按钮绑定事件$('#multiDel').click(function () {var idList=[];// 查找所有属性值为checked的标签多选框$('#tbBody').find(':checked').each(function () {var v = $(this).val();idList.push(v);});$.ajax({url:url,type:'delete',data:JSON.stringify(idList), // 将列表转换成json字符发送给后台sucess:function (arg) {console.log(arg)}})});

views:

# 采用restful(面向资源编程)风格
def curd_json(request):"""ajax请求方法:param request::return:"""if request.method == 'DELETE':id_list = json.loads(str(request.body, encoding='utf-8')) # 需要从body请求体中取出数据print(id_list)return HttpResponse('--')elif request.method == 'POST':passelif request.method == 'GET':

十七、不刷新页面,完成数据刷新功能


nb-list.js

 // {#生成表头字段#}function initTableHeader(tableConfig) {$('#tbHead').empty()    // 清除该标签内的所有内容var tr = document.createElement('tr')    // 生成tr标签# 略……// 刷新页面按钮绑定事件$('#refresh').click(function () {initial(url);})

十八、增删改查插件之批量更新


nb-list.js

// {#生成表格数据信息#}function initTableBody(serverList, tableConfig) {$('#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);   // 生成每一行数据的id值放入标签属性中// 退出编辑模式function trOutEdit($tr) {# 略……var option = $(this).find('select')[0].selectedOptions;  // 将jquery对象转换为DOM对象,调用selectOptions获得select标签中的options标签$(this).attr('new-origin', $(option).val());    // 将修改的值放入标签属性中$(this).html($(option).text());# 略……// 保存按钮绑定事件$('#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 () {  // 获得每一个tr标签// $(this) = trvar $tr = $(this);var row_dict = {};var flag = false;var nid = $tr.attr('nid');$tr.children().each(function () {   // 获得每一个td标签if ($(this).attr('edit-enable')){   // 属于可编辑的标签if($(this).attr('edit-type') == 'select'){// td标签属于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 { // td标签属于input框时var newData = $(this).text();var oldData = $(this).attr('origin');console.log(newData, oldData)if (newData != oldData){var name =$(this).attr('name'); // 获得字段名称row_dict[name] = newData;   // 封装成字典格式数据,便于数据库查询flag = true;}}}});if(flag){row_dict['id'] = nid;   // 获得该条数据的id}all_list.push(row_dict);    // 往数据库插入数据时需要用到的字典列表});// 通过Ajax提交后台$.ajax({url:url,type:'PUT',data:JSON.stringify(all_list),sucess:function (arg) {console.log(arg)}})});

views:

def curd_json(request):"""ajax请求方法:param request::return:"""if request.method == 'DELETE':id_list = json.loads(str(request.body, encoding='utf-8'))  # 需要从body请求体中取出数据print(id_list)return HttpResponse('--')elif request.method == 'POST':passelif request.method == 'PUT':all_list = json.loads((str(request.body, encoding='utf-8')))   # 编码成字符串print(all_list)return HttpResponse('---')elif request.method == 'GET':table_config = [  # 配置文件,用于前端页面数据定制显示# 生成checkbox多选框字段{'q': None,  # 不作为数据库查询字段'title': '选择','display': True,'text': {'tpl': "<input type='checkbox' value='{n1}' />",'kwargs': {'n1': '@id', }},'attrs': {'nid': '@id'}},# 生成id字段{'q': 'id',  # 用于数据库查询字段名'title': 'ID',  # 用于前端页面中表头字段名的显示'display': False,  # display表示该字段在前端页面表格表头是否显示'text': {  # text用来将数据库中取出的值进行字符串格式化'tpl': '{n1}',  # 用于生成格式化字符串中的占位符模板'kwargs': {'n1': '@id'}  # 占位符中具体的id数值,用于生成链接中对单条数据的操作},'attrs': {'k1': 'v1', 'k2': '@hostname'}  # 为前端标签添加属性及属性值},{'q': 'hostname','title': '主机名','display': True,'text': {'tpl': '{n1}-{n2}','kwargs': {'n1': '@hostname', 'n2': '@id'}},'attrs': {'edit-enable': 'true', 'k2': '@hostname', 'origin': '@hostname', 'name': 'hostname'}# edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改},# 页面显示 操作: 删除,编辑,a标签生成{'q': None,'title': '操作','display': True,'text': {'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs': {'nid': '@id'},},'attrs': {'k1': 'v1', 'k2': '@hostname'}},
]# 组装数据库查询所需的字段value_list = []for row in table_config:if not row['q']:continuevalue_list.append(row['q'])server_list = models.Server.objects.values(*value_list)  # 传入列表获得字典格式数据ret = {'server_list': list(server_list),  # 将Querylist转换成列表'table_config': table_config,}return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

十九、 增删改查插件之快速实现IDC基本增删改查


urls:

    # idc机房信息展示url(r'^idc.html$', views.idc),url(r'^idc_json.html$', views.idc_json),

views:


def idc(request):"""跳转到idc页面:param request::return:"""return render(request, 'idc.html')def idc_json(request):if request.method == 'DELETE':id_list = json.loads(str(request.body, encoding='utf-8'))print(id_list)return HttpResponse('删除成功')elif request.method == 'PUT':all_list = json.loads(str(request.body, encoding='utf-8'))print(all_list)return HttpResponse('保存成功')elif request.method == 'GET':from backend.page_config import idcvalues_list = []for row in idc.table_config:    # 从配置文件中获取数据库查询所需的字段if not row['q']:continuevalues_list.append(row['q'])server_list = models.IDC.objects.values(*values_list)ret={'server_list':list(server_list),'table_config':idc.table_config,'global_dict':{}}return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

page_config:

table_config = [  # 配置文件,用于前端页面数据定制显示# 生成checkbox多选框字段{'q': None,  # 不作为数据库查询字段'title': '选择','display': True,'text': {'tpl': "<input type='checkbox' value='{n1}' />",'kwargs': {'n1': '@id', }},'attrs': {'nid': '@id'}},# 生成id字段{'q': 'id',  # 用于数据库查询字段名'title': 'ID',  # 用于前端页面中表头字段名的显示'display': False,  # display表示该字段在前端页面表格表头是否显示'text': {  # text用来将数据库中取出的值进行字符串格式化'tpl': '{n1}',  # 用于生成格式化字符串中的占位符模板'kwargs': {'n1': '@id'}  # 占位符中具体的id数值,用于生成链接中对单条数据的操作},'attrs': {'k1': 'v1', 'k2': '@id'}  # 为前端标签添加属性及属性值},{'q': 'name','title': '机房名','display': True,'text': {'tpl': '{n1}','kwargs': {'n1': '@name',}},'attrs':{'edit-enable':'true','origin':'@name','name':'name'}# edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改},# 页面显示 操作: 删除,编辑,a标签生成{'q': 'floor','title': '楼层','display': True,'text': {'tpl': "{n1}",'kwargs': {'n1': '@floor'},},'attrs':{'edit-enable':'true','origin':'@floor','name':'floor'}},]

html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css" />
</head>
<body>
<div style="width: 700px; margin: 0 auto"><h1>IDC列表</h1>{% include 'nb-tpl.html' %}
</div><script src="/static/jquery-1.12.4.js"></script>
<script src = "/static/nb-list.js"></script>
<script>{#调用自定义的jQuery函数#}$.xx('/backend/curd_json.html');
</script>
</body>
</html>

nb-tpl.html页面组件:

<div class="btn-group" role="group" aria-label="..."><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="#" role="button">添加</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"></thead><tbody id="tbBody"></tbody></table>

nb-list.js 自定义js文件:

// 自定义js匿名函数,属于自动调用,只有内部调用,防止当成插件时的同名冲突
(function (jq) {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];});};// {#页面加载时自动发送ajax请求#}function initial(url) {$.ajax({url: url,type: 'GET',// {#将响应的字符串数据转换成字典格式#}dataType: 'JSON',success: function (arg) {// 将 (1, '服务器')……等数据作成全局常量$.each(arg.global_dict, function (k ,v) {GLOBAL_DICT[k] = v});// {#生成表头字段#}initTableHeader(arg.table_config);// {#生成表格数据#}initTableBody(arg.server_list, arg.table_config);}})}// {#生成表头字段#}function initTableHeader(tableConfig) {$('#tbHead').empty()    // 清除该标签内的所有内容var tr = document.createElement('tr')    // 生成tr标签// {#循环生成字段表头#}$.each(tableConfig, function (k, v) {if (v.display) { // 为Ture时需要展示var tag = document.createElement('th');tag.innerHTML = v.title$('#tbHead').find('tr').append(tag);}})}// {#生成表格数据信息#}function initTableBody(serverList, tableConfig) {$('#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) {if (rrow.display) {      // 是否需要展示该字段对应的内容// 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"var td = document.createElement('td');// rrow['q']// rrow['text']// rrow.text.tpl = "asdf{n1}sdf"// rrow.text.kwargs = {'n1':'@id','n2':'@@123'}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);  // 获得数据表中的字段名 例device_type_choicesvar nid = row[rrow.q]   // 通过自定义的配置字典,获得数据表中该条数据的id值$.each(GLOBAL_DICT[global_dict_key], function (gk, gv) {if(gv[0] == nid){av = gv[1]; // av = '服务器'}})}// {#@表示需要进行字符串格式化#}else if (vvv[0] == '@') {// {#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}av = row[vvv.substring(1, vvv.length)];}newKwargs[kkk] = av;});// {#通过自定义的扩展方法进行字符串格式化#}var newText = rrow.text.tpl.format(newKwargs);td.innerHTML = newText;// 在标签中添加属性$.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) {if ($('#inOutEditMode').hasClass('btn-warning')){   // 是否进入了编辑模式$tr.find('td[edit-enable="true"]').each(function () {   // 找到tr标签下所有td标签中属性为edit-enable=true的元素,并循环它// $(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');    // 获得当前标签中的origin属性的值$.each(deviceTypeChoices, function (k, v) {  //  v的值为 (1, '服务器'),var option = document.createElement('option');$(option).text(v[1]);  // 为option标签添加文本值$(option).val(v[0]);  // 为option标签添加属性值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) 每一个tdvar editType = $(this).attr('edit-type');   // 获得标签类型if(editType == 'select'){var option = $(this).find('select')[0].selectedOptions;  // 将jquery对象转换为DOM对象,调用selectOptions获得select标签中的options标签$(this).attr('new-origin', $(option).val());    // 将修改的值放入标签属性中$(this).html($(option).text());}else {var inputVal = $(this).find('input').val();    // 获得tr标签中所有input标签的值$(this).html(inputVal);  // 为当前td标签添加html格式内容}})}jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用xx: function (url) {// {#通过ajax异步请求获得初始化数据#}initial(url)// 通过js控制,控制标签类型,完成进入编辑模式功能// 在tbBody标签范围中为所有checkbox添加click事件$('#tbBody').on('click', ':checkbox', function () {// 检测多选框是否已经被选中var $tr = $(this).parent().parent()   // 通过checkbox标签获得tr标签中的元素if ($(this).prop('checked')){   // prop()获得标签属性值// 进入编辑模式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 {$(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 () {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 () {var idList=[];// 查找所有属性值为checked的标签多选框$('#tbBody').find(':checked').each(function () {var v = $(this).val();idList.push(v);});$.ajax({url:url,type:'DELETE',data:JSON.stringify(idList), // 将列表转换成json字符发送给后台sucess: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 () {  // 获得每一个tr标签// $(this) = trvar $tr = $(this);var row_dict = {};var flag = false;var nid = $tr.attr('nid');$tr.children().each(function () {   // 获得每一个td标签if ($(this).attr('edit-enable')){   // 属于可编辑的标签if($(this).attr('edit-type') == 'select'){// td标签属于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 { // td标签属于input框时var newData = $(this).text();var oldData = $(this).attr('origin');console.log(newData, oldData)if (newData != oldData){var name =$(this).attr('name'); // 获得字段名称row_dict[name] = newData;   // 封装成字典格式数据,便于数据库查询flag = true;}}}});if(flag){row_dict['id'] = nid;   // 获得该条数据的id}all_list.push(row_dict);    // 往数据库插入数据时需要用到的字典列表});// 通过Ajax提交后台$.ajax({url:url,type:'PUT',data:JSON.stringify(all_list),sucess:function (arg) {console.log(arg)}})});}});
})(jQuery)  // 传入jQeury对象

二十、 CMDB搜索功能


nb-search.html 自定义搜索组件:

 <div class="search-list clearfix"  style="position: relative;">
{#        搜索框#}<div class="search-btn col-md-offset-10 col-md-2" style="position: absolute ;bottom:1px;text-align: right "><input id="doSearch" type="button" value="搜索" class="btn btn-primary"></div>{#           加号按钮+下拉框 +输入框     #}<div class="search-item col-md-10 clearfix" style="position: relative;height: 35px" >
{#            加号部分#}<div style="position: absolute;left: 0;"><a 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;"><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>
{#                搜索输入框#}
{#                <input type="text" class="form-control" aria-label="..." >#}</div></div></div>

nb-list.js 自定义js文件:


var CREATE_SEARCH_CONDITION = true; // 用来控制点击搜索后,保留搜索框中的内容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);    // 为列表添加值时采用push()}else {condition[name] = [value]; // 组装成字典,值为列表格式}});return condition;}// {#页面加载时自动发送ajax请求#}function initial(url) {// 执行一个函数,获取当前搜索条件var searchCondition = getSearchCondition();$.ajax({url: url,type: 'GET',// {#将响应的字符串数据转换成字典格式#}dataType: 'JSON',data:{condition: JSON.stringify(searchCondition)},success: function (arg) {// 将 (1, '服务器')……等数据作成全局常量$.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;// 生成搜索类型的下拉框$.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]);  // (1,主机1),(2,主机2)……$(op).val(v[0]);$(sel).append(op);})$('.input-group').append(sel);}else {     // 内容输入框变为input框var inp = document.createElement('input')$(inp).attr('name', searchConfig[0].name);$(inp).attr('type', 'text');$(inp).attr('class', 'form-control');$('.input-group').append(inp);}}}jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用xx: function (url) {// {#通过ajax异步请求获得初始化数据#}initial(url)//  点击不同的搜索类型生成不同的搜索形式(input 或者 select)$('.search-list').on('click', 'li', function () {var wenben = $(this).text();var searchType = $(this).attr('search_type');var name = $(this).attr('name');var globalName = $(this).attr('global_name');// 把显示替换    prev()获得同胞元素$(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 {// 搜索内容类型为input框var inp = document.createElement('input');$(inp).attr('name', name);$(inp).attr('type', 'text');$(inp).attr('class', 'form-control');$(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)  // 传入jQeury对象

views:


def get_data_list(request,model_cls,table_config):"""根据搜索条件进行查询:param request::param model_cls::param table_config::return:"""values_list = []for row in table_config:if not row['q']:continuevalues_list.append(row['q'])from django.db.models import Qcondition = request.GET.get('condition')condition_dict = json.loads(str(condition))con = Q()for name,values in condition_dict.items():  # {'hostname__contains': ['c1', 'c2']}ele = Q() # select xx from where cabinet_num=sdf or cabinet_num='123'ele.connector = 'OR'for item in values: #  ['c1', 'c2']ele.children.append((name,item))con.add(ele, 'AND') # (AND: (OR: ('hostname__contains', 'c1'), ('hostname__contains', 'c2')))server_list = model_cls.objects.filter(con).values(*values_list)return server_listdef curd(request):"""进入到curd.html页面:param request::return:"""return render(request, 'curd.html')def curd_json(request):"""ajax请求方法:param request::return:"""if request.method == 'DELETE':id_list = json.loads(str(request.body, encoding='utf-8'))  # 需要从body请求体中取出数据print(id_list)return HttpResponse('--')elif request.method == 'POST':passelif request.method == 'PUT':all_list = json.loads((str(request.body, encoding='utf-8')))   # 编码成字符串print(all_list)return HttpResponse('---')elif request.method == 'GET':from backend.page_config import curd as curdConfigserver_list = get_data_list(request, models.Server, curdConfig.table_config)ret = {'server_list': list(server_list),  # 将Querylist转换成列表'table_config': curdConfig.table_config,'search_config': curdConfig.search_config,'global_dict':{ # 用于生成下拉框'device_type_choices':models.Asset.device_type_choices,'device_status_choices':models.Asset.device_status_choices,}}return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

curd.py 配置文件内容:

table_config = [  # 配置文件,用于前端页面数据定制显示# 生成checkbox多选框字段{'q': None,  # 不作为数据库查询字段'title': '选择','display': True,'text': {'tpl': "<input type='checkbox' value='{n1}' />",'kwargs': {'n1': '@id', }},'attrs': {'nid': '@id'}},# 生成id字段{'q': 'id',  # 用于数据库查询字段名'title': 'ID',  # 用于前端页面中表头字段名的显示'display': False,  # display表示该字段在前端页面表格表头是否显示'text': {  # text用来将数据库中取出的值进行字符串格式化'tpl': '{n1}',  # 用于生成格式化字符串中的占位符模板'kwargs': {'n1': '@id'}  # 占位符中具体的id数值,用于生成链接中对单条数据的操作},'attrs': {'k1': 'v1', 'k2': '@hostname'}  # 为前端标签添加属性及属性值},{'q': 'hostname','title': '主机名','display': True,'text': {'tpl': '{n1}-{n2}','kwargs': {'n1': '@hostname', 'n2': '@id'}},'attrs': {'edit-enable': 'true', 'k2': '@hostname', 'origin': '@hostname', 'name': 'hostname'}# edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改},# 页面显示 操作: 删除,编辑,a标签生成{'q': None,'title': '操作','display': True,'text': {'tpl': "<a href='/del?nid={nid}'>删除</a>",'kwargs': {'nid': '@id'},},'attrs': {'k1': 'v1', 'k2': '@hostname'}},]# 搜索框部分所需的配置
# hostname__contains用于实现模糊查询
search_config=[{'name':'hostname__contains', 'text':'主机名','search_type':'input'},# {'name':'sn__contains', 'text':'SN号','search_type':'input'},
]

二十一、HightCharts制图框架


urls:

    url(r'^chart.html$', views.chart ),

views:

def chart(request):"""跳转到页面:param request::return:"""return render(request, 'chart.html')

chart.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div id="i1"></div><script src="/static/jquery-1.12.4.js"></script>
<script src="/static/Highcharts-5.0.12/code/highcharts.js"></script>
<script>// jQuery == $Highcharts.setOptions({global: {useUTC: false   // 不使用UTC时区}});var chart = new Highcharts.Chart('i1', {title: {text: '大标题',x: 0},subtitle: {text: '数据来源: WorldClimate.com',x: 0},chart: {events: {load: function (e) {// 图标加载时,执行的函数console.log('图标加载时,执行的函数')}}},credits: {enable: true,position: {align: 'right',verticalAlign: 'bottom'},text: 'oldboy',href: 'http://www.oldboyedu.com'},xAxis: {// 适用于固定x轴type: 'datetime',labels: {formatter: function () {return Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.value);},rotation: 30}},yAxis: {title: {text: '数值'}},tooltip: { // 工具提示pointFormatter: function (e) {var tpl = '<span style="color:' + this.series.color + '">哦哦哦哦哦小</span> ' + this.series.name + ': <b>' + this.y + '个</b><br/>';return tpl;},useHTML: true},plotOptions: {series: { // 系列数据cursor: 'pointer',events: {click: function (event) {// 点击某个指定点时,执行的事件console.log(this.name, event.point.x, event.point.y);}}}},series: [{name: '洛杉矶',data: [[1501689804077.358, 8.0],[1501689814177.358, 6.9],[1501689824277.358, 16.9],[1501689834377.358, 11.9]]},{name: '南京',data: [[1501689804077.358, 18.0],[1501689814177.358, 16.9],[1501689824277.358, 6.9],[1501689834377.358, 21.9]]}]});</script>
</body>
</html>

十三、Restful 面向资源编程,实现接口开发


urls:

    # restful面向资源编程url(r'^servers.html$',  views.servers),url(r'^servers/(\d+).html$',  views.servers_detail),

views:


from django.http import JsonResponse
def servers(request):# http://127.0.0.1:8000/api/servers.html GET:   获取服务器列表# http://127.0.0.1:8000/api/servers.html POST:  创建服务器# http://127.0.0.1:8000/api/servers/1.html GET: 获取单条信息# http://127.0.0.1:8000/api/servers/1.html DELETE: 删除单条信息# http://127.0.0.1:8000/api/servers/1.html PUT: 更新if request.method == 'GET':v = models.Server.objects.values('id', 'hostname')server_list = list(v)return JsonResponse(server_list, safe=False)    # 默认只能发送字典格式数据elif request.method == 'POST':return JsonResponse(status=201)def servers_detail(request,nid):"""获得单条数据的操作:param request::param nid::return:"""if request.method == 'GET':obj = models.Server.objects.filter(id=nid).first()return HttpResponse('...')elif request.method == "DELETE":models.Server.objects.filter(id=nid).delete()return HttpResponse()elif request.method == 'PUT':request.bodymodels.Server.objects.filter(id=nid).update()

CMDB资产管理系统相关推荐

  1. Python之CMDB资产管理系统

    最近正好在给公司做CMDB资产管理系统,现在做的也差不多了,现在回头吧思路整理下. CMDB介绍 CMDB --Configuration Management Database 配置管理数据库, C ...

  2. Python运维开发(CMDB资产管理系统)--环境部署(上)

    服务器准备 操作系统 内核 IP CentOS Linux 7 3.10 192.168.1.108 升级sqlite3 由于centos7默认sqlite3版本比较低,启动项目会因为sqlite3版 ...

  3. Python运维开发(CMDB资产管理系统)——环境部署(下)

    创建管理员用户 (web) [root@k8s-node02 myweb]# python manage.py createsuperuser --username admin --email adm ...

  4. Python运维开发(CMDB资产管理系统)——Pycharm部署

    下载pycharm安装包 下载地址 https://www.jetbrains.com/pycharm/download 下载专业版64位 安装 选择Next后开始安装 激活pycharm 将插件拖入 ...

  5. Python运维开发(CMDB资产管理系统)——Python基础数据类型

    Python基础数据类型 字符串(可以通过单引号,双引号,三个双引号来表示) 布尔(True和False) 整数 浮点数 列表 定义一个列表 列表常用的一些函数 append(向列表中添加元素,元素可 ...

  6. linux搭建资产管理系统,GitHub - SuperLandy/cmdb: IT资产管理系统

    IT资产管理系统 说明 $ 开头的行表示需要执行的命令 环境 系统: CentOS 7 目录: /opt 数据库: mariadb5.5 web: nginx 开始安装 $ firewall-cmd ...

  7. 资产管理系统 CMDB 详解

    两年前笔者在一个中小型互联网公司做运维,当时我们经理在机房,花了半天找一台服务器,但是服务器搞错了,悲剧了^.^! 当时我们的做法是用了一个 Excel,很多时候更新不及时,重启一台机器.拔一根网线都 ...

  8. python cmdb 资产管理 antoclient_资产管理系统 CMDB 详解

    两年前笔者在一个中小型互联网公司做运维,当时我们经理在机房,花了半天找一台服务器,但是服务器搞错了,悲剧了^.^! 当时我们的做法是用了一个 Excel,很多时候更新不及时,重启一台机器.拔一根网线都 ...

  9. IT资产管理系统的技术优势!

    IT资产全生命周期概念 为了更好的应对云时代IT资产管理面临的问题,我们提出了IT资产全生命周期管理概念:以CMDB为核心,贯穿资产生命期和资源生命期的全周期管理:将IT设备划分为有形设备资产 + 虚 ...

最新文章

  1. 红盟过客提到的 CCIE 必读书籍
  2. 字节流读数据(一次读一个字节数据)
  3. hdfs 操作 入门api
  4. 如何从手机或PC将游戏下载到PlayStation 4
  5. 在JavaScript中实现命名空间
  6. C++中默认选中预编译头#includestdafx.h作用
  7. u8 附件上传后存放路径_用友U8生产不良退料案例教程
  8. OpenCV_轮廓例子
  9. 智能电能计量管理系统
  10. 基于springboot网上订餐系统设计与实现
  11. python数据分析有哪些模型-python数据挖掘的基本流程有哪些?
  12. 2020第六届上海市大学生网安大赛Misc|writeup
  13. RxJava串行执行任务
  14. 《罗曼蒂克消亡史》影评
  15. Matlab基于动态粒子群算法的动态环境寻优算法 基本粒子群算法首先是在解空间中随机初始化所有粒子
  16. greenplum安装方法详解
  17. Dynamics 365 on-premises9.0版本开放下载,附上8.2升级9.0过程
  18. 脑壳疼的Webpack-tapable
  19. 网站绑定域名后不能用IP直接访问了?
  20. java 代码封装_封装 java代码

热门文章

  1. 人脸识别《一》opencv人脸识别之人脸检测
  2. 所有用户登陆后都在桌面上显示计算机图标,如何在Win10桌面上显示计算机.控制面板.网络.用户的文件图标?...
  3. 【知识点总结】电力电子技术——第一讲
  4. 大天使黎明服务器维护,金山猎豹大天使之剑全服4月17日维护公告
  5. php如何做防抖,别抖,OK? 操作系统抖动现象、网络抖动与延迟、函数抖动之防抖与节流,串讲...
  6. java poi excel导出2003版改成2007版本的时候报错
  7. 步进电机基本原理、分类、基本参数、应用场景
  8. 计算机毕业设计ssm基于b_s架构的实习管理系统
  9. signal 11 linux,关于运行时Signal 11 Caught的错误
  10. uniapp 小程序用高德地图sdk