CMDB資產管理系統

1、遠程服務器資產信息採集方案css

實現方案一:agent——server服務端模式html

應用場景:多應用於服務器數量多狀況下,效率比ssh方式高前端

客戶端:python

################### 方式一:Agent,每一臺服務器一份 ####################
import subprocess
v1 = subprocess.getoutput('ipconfig') # 開啓一個子進程
value1= v1[20:30]jquery

v2 = subprocess.getoutput('dir') # 開啓一個子進程
value2= v2[0:5]linux

import requests
url = 'http://127.0.0.1:8001/asset.html'
response = requests.post(url, data={'k1':value1, 'k2':value2}) # 發送數據
print(response)ajax


服務端:
url(r'^asset.html$', views.asset),
1
from django.shortcuts import render,HttpResponseshell

def asset(request):
if request.method == "POST":
print(request.POST)
# 寫入到數據
return HttpResponse('1002')
else:
return HttpResponse('姿式不對')

實現方案二:經過SSH遠程鏈接服務器,使用Paramiko建立中控機數據庫

pip3 install paramikodjango

應用場景:服務器數量很少的時候,運行效率相對低,但維護簡單,不須要每臺服務安裝客戶端
客戶端:
# #################### 方式二: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命令
2、採集器部分開發
目錄結構:

 

3、 高級配置文件處理
可執行文件: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() # 將實例化的對象做爲屬性值

3、 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']

4、資產入庫數據庫表設計
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


5、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('')

7、資產入庫處理 (以硬盤爲例)
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('')

8、牛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':
略……

9、對資產信息進行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)

10、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>

11、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>
# 略……

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

十3、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)

# 略……

十4、增刪改查插件之當前行進入編輯模式
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對象

十5、增刪改查插件之全選反選取消以及進入編輯模式按鈕
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);
}
});
}
})
#略……

十6、增刪改查插件之批量刪除數據
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':

十8、增刪改查插件之批量更新
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))

十9、 增刪改查插件之快速實現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對象

二10、 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'},
]

二11、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>

十3、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()

相關文章
相關標籤/搜索