jokezl

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

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

<!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获得数据呈现在页面中
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\': "<a href=\'/del?nid={nid}\'>删除</a>",
\'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:

<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\']:
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:

<!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\']:
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\': "<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\']:
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-}
/*
<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_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

# 略……
<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\':
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-}
/*
<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) = 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\': "<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\']:
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\': "<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_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 自定义搜索组件:

<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\']:
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\': "<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 ),
1
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.body
models.Server.objects.filter(id=nid).update()

分类:

技术点:

相关文章: