python资产管理系统_CMDB资产管理系统

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

实现方案一: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),

1

from django.shortcuts import render,HttpResponse

def 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 requests

response = 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_settings

class 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:

return

custom_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 os

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

# ##############插件所需的配置参数################

MODE = ‘AGENT‘ # 采用agent模式采集服务器信息

# MODE = ‘SALT‘ # 采用salt模式采集服务器信息

# MODE = ‘SSH‘ # 采用SSH模式采集服务器信息

DEBUG = True

SSH_USER = ‘root‘ # 连接远程服务器的用户名

SSH_PWD = ‘root‘ # 连接远程服务器的密码

SSH_KEY = ‘/XX/XX/XX‘ # 通过公钥私钥来连接远程服务器实现免密登陆

SSH_PORT = 22

PLUGINS_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_settings

class 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:

return

custom_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 script

if __name__ == ‘__main__‘:

script.run()

script.py脚本文件:

from lib.conf.config import settings

from .client import Agent

from .client import SSHSALT

def 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 ThreadPoolExecutor

class 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‘] = certname

self.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 None

return result[‘data‘]

# 执行服务器信息采集,并将该信息发送给API

def 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 traceback

class PluginManager(object):

def __init__(self, hostname=None): # 为agent/salt模式预留的主机名参数值

self.hostname = hostname

self.plugin_dict = settings.PLUGINS_DICT

self.mode = settings.MODE # 采集模式

self.debug = settings.DEBUG

if self.mode == ‘SSH‘:

self.ssh_user = settings.SSH_USER

self.ssh_port = settings.SSH_PORT

self.ssh_pwd = settings.SSH_PWD

self.ssh_key = settings.SSH_KEY

def 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‘] = result

except 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] = ret

return 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 subprocess

output = subprocess.getoutput(cmd)

return output

def __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 subprocess

output = subprocess.getoutput(salt_cmd)

return output

basic.py采集基础信息:

class Basic:

"""

获取服务器基本信息(服务器名称...)

"""

def __init__(self):

pass

@classmethod

def 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 os

class Board:

"""

获取服务器主板信息

"""

def __init__(self):

pass

@classmethod

def 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 settings

class Cpu:

"""

获取服务器CPUT信息

"""

def __init__(self):

pass

@classmethod

def 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‘] += 1

elif key == ‘physical id‘:

cpu_physical_set.add(value)

elif key == ‘model name‘:

if not response[‘cpu_model‘]:

response[‘cpu_model‘] = value

response[‘cpu_physical_count‘] = len(cpu_physical_set)

return response

disk.py 采集硬盘信息:

import re

import os

from lib.conf.config import settings

class Disk:

"""

获取服务器硬盘信息

"""

def __init__(self):

pass

@classmethod

def 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():

continue

if len(row.split(‘:‘)) != 2:

continue

key, 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_dict

return response

@staticmethod

def 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 value

return False

memory.py 采集内存信息:

#!/usr/bin/env python

# -*- coding:utf-8 -*-

import os

from lib import convert

from lib.conf.config import settings

class Memory(object):

"""

获取服务器内存信息

"""

def __init__(self):

pass

@classmethod

def 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:

continue

if item.startswith(‘#‘):

continue

segment = {}

lines = item.split(‘\n\t‘)

for line in lines:

if not line.strip():

continue

if 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‘]] = segment

return ram_dict

采集网卡信息:

#!/usr/bin/env python

# -*- coding:utf-8 -*-

import os

import re

from lib.conf.config import settings

class Nic(object):

"""

获取服务器网卡信息

"""

def __init__(self):

pass

@classmethod

def 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_info

def 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 ifaces

def which(self, exe):

def _is_executable_file_or_link(exe):

# check for os.X_OK doesn‘t suffice because directory may executable

return (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 fullpath

return exe

# default path based on busybox‘s default

default_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_path

return None

def _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 -= 8

else:

netmask += ‘{0:d}‘.format(256 - (2 ** (8 - cidr_bits)))

cidr_bits = 0

return netmask

def _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) & 0x55555555

x = ((x >> 2) & 0x33333333) + (x & 0x33333333)

x = ((x >> 4) + x) & 0x0f0f0f0f

x += x >> 8

x += x >> 16

return x & 0x0000003f

def _interfaces_ip(self, out):

‘‘‘

Uses ip to return a dictionary of interfaces with various information about

each (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, broadcast

based on the current set of cols

‘‘‘

brd = None

if ‘/‘ in value: # we have a CIDR in this address

ip, cidr = value.split(‘/‘) # pylint: disable=C0103

else:

ip = value # pylint: disable=C0103

cidr = 32

if 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 = None

data = dict()

for line in group.splitlines():

if ‘ ‘ not in line:

continue

match = re.match(r‘^\d*:\s+([\w.\-]+)(?:@)?([\w.\-]+)?:\s+‘, line)

if match:

iface, parent, attrs = match.groups()

if ‘UP‘ in attrs.split(‘,‘):

data[‘up‘] = True

else:

data[‘up‘] = False

if parent and parent in right_keys:

data[parent] = parent

continue

cols = 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‘] = ipaddr

addr_obj[‘netmask‘] = netmask

addr_obj[‘broadcast‘] = broadcast

data[‘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, brd

elif type_.startswith(‘link‘):

data[‘hwaddr‘] = value

if iface:

if iface.startswith(‘pan‘) or iface.startswith(‘lo‘) or iface.startswith(‘v‘):

del iface, data

else:

ret[iface] = data

del iface, data

return ret

def 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 models

class UserProfile(models.Model):

"""

用户信息

"""

name = models.CharField(u‘姓名‘, max_length=32)

email = models.EmailField(u‘邮箱‘)

phone = models.CharField(u‘座机‘, max_length=32)

mobile = models.CharField(u‘手机‘, max_length=32)

class Meta:

verbose_name_plural = "用户表"

def __str__(self):

return self.name

class AdminInfo(models.Model):

"""

用户登陆相关信息

"""

user_info = models.OneToOneField("UserProfile", 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.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‘, on_delete=‘‘)

manager = models.ForeignKey(‘UserGroup‘, verbose_name=‘系统管理员‘, related_name=‘m‘, on_delete=‘‘)

class Meta:

verbose_name_plural = "业务线表"

def __str__(self):

return self.name

class IDC(models.Model):

"""

机房信息

"""

name = models.CharField(‘机房‘, max_length=32)

floor = models.IntegerField(‘楼层‘, default=1)

class Meta:

verbose_name_plural = "机房表"

def __str__(self):

return self.name

class Tag(models.Model):

"""

资产标签

"""

name = models.CharField(‘标签‘, max_length=32, unique=True)

class Meta:

verbose_name_plural = "标签表"

def __str__(self):

return self.name

class Asset(models.Model):

"""

资产信息表,所有资产公共信息(交换机,服务器,防火墙等)

"""

device_type_choices = (

(1, ‘服务器‘),

(2, ‘交换机‘),

(3, ‘防火墙‘),

)

device_status_choices = (

(1, ‘上架‘),

(2, ‘在线‘),

(3, ‘离线‘),

(4, ‘下架‘),

)

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

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

cabinet_num = models.CharField(‘机柜号‘, max_length=30, null=True, blank=True)

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

idc = models.ForeignKey(‘IDC‘, verbose_name=‘IDC机房‘, null=True, blank=True, 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.hostname

class 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.slot

class NIC(models.Model):

"""

网卡信息

"""

name = models.CharField(‘网卡名称‘, max_length=128)

hwaddr = models.CharField(‘网卡mac地址‘, max_length=64)

netmask = models.CharField(max_length=64)

ipaddrs = models.CharField(‘ip地址‘, max_length=256)

up = models.BooleanField(default=False)

server_obj = models.ForeignKey(‘Server‘,related_name=‘nic‘, on_delete=‘‘)

class Meta:

verbose_name_plural = "网卡表"

def __str__(self):

return self.name

class Memory(models.Model):

"""

内存信息

"""

slot = models.CharField(‘插槽位‘, max_length=32)

manufacturer = models.CharField(‘制造商‘, max_length=32, null=True, blank=True)

model = models.CharField(‘型号‘, max_length=64)

capacity = models.FloatField(‘容量‘, null=True, blank=True)

sn = models.CharField(‘内存SN号‘, max_length=64, null=True, blank=True)

speed = models.CharField(‘速度‘, max_length=16, null=True, blank=True)

server_obj = models.ForeignKey(‘Server‘,related_name=‘memory‘, on_delete=‘‘)

class Meta:

verbose_name_plural = "内存表"

def __str__(self):

return self.slot

class 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‘)),

1

api模块:

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

1

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 = [] # 获得之前的硬盘数据中的插槽ID

for 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)

###################从硬盘数据表中删除数据#################3

if 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 Series

disk_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 hashlib

api_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 AES

def encrypt(message):

"""

AES加密资产数据

:param message:

:return:

"""

key = settings.DATA_KEY

cipher = 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 = 16

else:

v3 = 16 - v2

for i in range(v3): # 需要为不是16倍数的字节数组添加字节,只至满足是16的倍数

ba_data.append(v3) # 添加的字节内容采用需补齐的长度值

final_data = ba_data.decode(‘utf-8‘) # 字节解码成字符串

msg = cipher.encrypt(final_data) # 对字符串进行加密,成为字节

return msg

def decrypt(msg):

"""

数据解密

:param msg:

:return:

"""

from Crypto.Cipher import AES

key = settings.DATA_KEY

cipher = 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 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进行加密,以便完成加密数据的比对

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 AES

key = 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), # 进入到数据展示页面

1

views:

def curd(request):

"""

进入到curd.html页面

:param request:

:return:

"""

return render(request, ‘curd.html‘)

html:

Title

urls:

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

1

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‘: "删除",

‘kwargs‘:{‘nid‘:‘@id‘},

}

},

]

# 组装数据库查询所需的字段

value_list = []

for row in table_config:

if not row[‘q‘]:

continue

value_list.append(row[‘q‘])

from datetime import datetime

from datetime import date

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方法

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:

$(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-}

/*

idhostncreate

*/

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);

})

}

十一、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‘: "删除",

‘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-}

/*

idhostncreate

*/

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:

# 略……

{#调用自定义的jQuery函数#}

$.xx(‘/backend/curd_json.html‘);

# 略……

十二、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‘: "删除",

‘kwargs‘: {‘nid‘: ‘@id‘}

}

},

]

# 用于数据库查询字段

value_list = []

for row in table_config:

if not row[‘q‘]:

continue

value_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:

资产列表

$.xx(‘/backend/asset_json.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-}

/*

idhostncreate

*/

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‘: "删除",

‘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‘: "",

‘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‘: "删除",

‘kwargs‘:{‘nid‘:‘@id‘},

},

‘attrs‘: {‘k1‘: ‘v1‘, ‘k2‘: ‘@hostname‘}

},

]

# 组装数据库查询所需的字段

value_list = []

for row in table_config:

if not row[‘q‘]:

continue

value_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‘: "",

‘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‘: "删除",

‘kwargs‘: {‘nid‘: ‘@id‘}

},

‘attrs‘: {‘k1‘: ‘v1‘, ‘k2‘: ‘@id‘}

},

]

# 用于数据库查询字段

value_list = []

for row in table_config:

if not row[‘q‘]:

continue

value_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-}

/*

idhostncreate

*/

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_choices

var 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) 每一个td

var 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

# 略……

服务器列表

全选

反选

取消

进入编辑模式

添加

删除

保存

# 略……

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‘:

pass

elif request.method == ‘GET‘:

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

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-}

/*

idhostncreate

*/

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) = tr

var $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‘:

pass

elif 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‘: "",

‘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‘: "删除",

‘kwargs‘: {‘nid‘: ‘@id‘},

},

‘attrs‘: {‘k1‘: ‘v1‘, ‘k2‘: ‘@hostname‘}

},

]

# 组装数据库查询所需的字段

value_list = []

for row in table_config:

if not row[‘q‘]:

continue

value_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 idc

values_list = []

for row in idc.table_config: # 从配置文件中获取数据库查询所需的字段

if not row[‘q‘]:

continue

values_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‘: "",

‘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:

Title

IDC列表

{% include ‘nb-tpl.html‘ %}

{#调用自定义的jQuery函数#}

$.xx(‘/backend/curd_json.html‘);

nb-tpl.html页面组件:

全选

反选

取消

进入编辑模式

添加

删除

刷新

保存

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-}

/*

idhostncreate

*/

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_choices

var 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) 每一个td

var 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) = tr

var $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 自定义搜索组件:

{# 搜索框#}

{# 加号按钮+下拉框 +输入框 #}

{# 加号部分#}

{# 下拉框+输入框部分#}

默认值

{# 搜索输入框#}

{# #}

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‘]:

continue

values_list.append(row[‘q‘])

from django.db.models import Q

condition = 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_list

def 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‘:

pass

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 curd as curdConfig

server_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‘: "",

‘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‘: "删除",

‘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 ),

1

views:

def chart(request):

"""

跳转到页面

:param request:

:return:

"""

return render(request, ‘chart.html‘)

chart.html

Title

// 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 = ‘哦哦哦哦哦小 ‘ + this.series.name + ‘: ‘ + this.y + ‘个
‘;

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]

]

}

]

});

十三、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.body

models.Server.objects.filter(id=nid).update()

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python-CMDB资产管理系统项目是基于Python编程语言开发的一种用于管理企业资产的信息系统。CMDB(Configuration Management Database)是一种用于记录和管理组织内所有硬件和软件资源的数据库。该项目的目标是通过实现一个功能强大且易于使用的资产管理系统,帮助企业更好地管理和维护其资产库。 该系统具备以下特点和功能: 1. 资产信息管理:可以录入和管理所有的企业资产信息,包括硬件设备、软件和网络设备等。用户可以根据自己的需求添加、修改和删除资产信息,并可以对资产进行分类和标记,便于查找和管理。 2. 资产追踪和监控:系统可以实时追踪和监控所有资产的状态和位置,帮助用户快速定位和管理资产。例如,用户可以查询特定设备的位置、使用状况和维护历史等信息。 3. 资产维护计划:系统可以为每个资产设置维护计划,包括定期检查、保养和维修等。用户可以根据资产的使用情况和维护需求制定和调整维护计划,以确保资产的正常运行。 4. 报表和统计分析:系统可以生成各种报表和统计分析图表,帮助用户了解和掌握资产的整体情况和趋势。用户可以根据报表和图表的结果做出决策和调整管理策略。 5. 安全和权限管理:系统支持用户权限管理,可以设置不同用户的访问权限,保护重要数据和操作不受未经授权的访问。 Python-CMDB资产管理系统项目使用Python语言开发,具有开源、跨平台和易于扩展等优势。同时,Python拥有丰富强大的库和框架,可以快速开发和调试系统。通过该项目,企业可以有效地管理和维护其资产,提高工作效率和资产利用率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值