前戏:项目目的
是一个运维自动化管理项目:
为了减少人工干预,降低人员成本 ---资产管理 --操作管理
避免人员直接操作服务器,使用后台去统一操作
一:实现方式
(一)Agent基于shell命令实现(在服务器去上安装Agent,在服务器本机定时自动去获取信息,发送到数据库,然后后台获取数据进行处理)
注意:一般我们不会直接将数据直接传递到数据库,会将数据传递到API接口先进行处理,过滤,然后才会发送到数据库。
注意:数据是由服务器agent主动发送至API
实现方案:
本地执行cmd命令。 方法一:os.system("命令") 不可以返回数据 方法二:subprocess模块,使用进程执行命令,可以获取到数据Popen("命令"),进程.stdout.read()<py2>或者直接getoutput("命令")<py3>
def agent(self,cmd): import subprocess try: ret = subprocess.getoutput(cmd) except AttributeError: sub = subprocess.Popen(args=cmd,shell=True,stdout=subprocess.PIPE) sub.wait() ret = sub.stdout.read() return ret
优点:信息采集快,由服务器自己采集信息传递到API
缺点:每台服务器都必须安装Agent
(二)SSH方法:使用paramiko模块,通过中控机服务器统一去获取指定服务器的信息。
def ssh(self,cmd): import paramiko #1.创建SSH对象 ssh = paramiko.SSHClient() #2.加上这句话不用担心选yes的问题,会自动选上 #3.用ssh连接远程主机时,第一次连接时会提示是否继续进行远程连接,选择yes ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",password="密码") #执行命令,获取结果到标准输入\出\错误流中 stdin,stdout,stderr = ssh.exec_command(cmd) #4.获取命令结果 result = stdout.read() #5.关闭连接 ssh.close()
def ssh(self,cmd): import paramiko #1.创建SSH对象 ssh = paramiko.SSHClient() #2.加上这句话不用担心选yes的问题,会自动选上 #用ssh连接远程主机时,第一次连接时会提示是否继续进行远程连接,选择yes ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #3.获取私钥 private_key = paramiko.RSAKey.from_private_key_file("文件:其中保存了私钥,用于解密") #4.通过私钥去连接远程服务器(前提是自己的公钥已经在对方的authorized_keys文件中,paramiko已实现) ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",pkey="私钥private_key") #5.执行命令,获取结果到标准输入\出\错误流中 stdin,stdout,stderr = ssh.exec_command(cmd) #6.获取命令结果 result = stdout.read() #7.关闭连接 ssh.close() return result
优点:不需要为服务器安装agent等软件
缺点:速度慢,适用于服务器少得时候
(三)saltstack:使用master对slave进行操作,基于列队实现(使用广)
# 1. 安装saltstack # rpm --import https://repo.saltstack.com/yum/redhat/6/x86_64/latest/SALTSTACK-GPG-KEY.pub # # """ Master: yum install salt-master Master准备: a. 配置文件,监听本机IP vim /etc/salt/master interface: 本机IP地址 b. 启动master /etc/init.d/salt-master start Slave: yum install salt-minion Slave准备: a. 配置文件,连接那个master vim /etc/salt/minion master: 远程master地址 b. 启动slave /etc/init.d/salt-minion start 2. 创建关系 查看 Master:salt-key -L Accepted Keys: Denied Keys: Unaccepted Keys: c1.com c2.com c3.com Rejected Keys: 接受 Master:salt-key -a c1.com Accepted Keys: c1.com c2.com Denied Keys: Unaccepted Keys: c3.com Rejected Keys: 3. 执行命令 master: salt 'c1.com' cmd.run 'ifconfig' import salt.client local = salt.client.LocalClient() result = local.cmd('c2.salt.com', 'cmd.run', ['ifconfig']) """
def salt(self,cmd): import subprocess result = subprocess.getoutput("Salt '主机名' cmd.run '"+cmd+"'") return result
def salt(self,cmd): import salt.client local = salt.client.LocalClient() result = local.cmd(self.hostname,'cmd.run',[cmd]) return result[self.hostname]
优点:快,开发成本低
缺点:依赖saltstack
(四)使用puppet(使用ruby写的,python不易扩展)
优点:自动汇报
缺点:ruby实现
二:代码实现(客户端)
bin目录(含启动文件)
from src.script import client import os,sys BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASEDIR) if __name__ == "__main__": client()
conf目录(含配置文件)
import os BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MODE = "Agent" #SSH Salt PLUGINS = { 'basic':'src.plugins.basic.BasicPlugin', 'disk':'src.plugins.disk.DiskPlugin', 'mem': 'src.plugins.mem.MemPlugin', # 'nic': 'src.plugins.nic.NicPlugin', } # 如果采用SSH方式,则需要配置SSH的KEY和USER SSH_PRIVATE_KEY = "/home/auto/.ssh/id_rsa" SSH_USER = "root" SSH_PORT = 22 # 用于API认证的KEY KEY = '299095cc-1330-11e5-b06a-a45e60bec08b' # 用于API认证的请求头 AUTH_KEY_NAME = 'auth-key' ASSET_API = "http://127.0.0.1:8000/API/asset" # Agent模式保存服务器唯一ID的文件 CERT_FILE_PATH = os.path.join(BASEDIR, 'conf', 'cert') TEST_MODE = True
...
lib目录(含有链接库)
class BaseResponse(object): def __init__(self): self.status = True self.message = None self.data = None self.error = None
import json as default_json from .response import BaseResponse def conv(obj): return obj.__dict__ class Json(object): @staticmethod def dumps(response): return default_json.dumps(response,default=conv)
log目录(记录日志)
client.py定义多种类,不同的方法与API交互from conf import setting from .client import * def client(): if setting.MODE == 'Agent': #"Agent" #SSH Salt cli = AutoAgent() elif setting.MODE == "SSH": cli = AutoSSH() elif setting.MODE == "Salt": cli = AutoSalt() else: raise Exception("配置信息出错") cli.process()
二级目录:plugins目录
from conf import setting from lib.response import BaseResponse def pack(hostname=None): response = {} for k,v in setting.PLUGINS.items(): file_name,cls_name = v.rsplit('.',maxsplit=1) pk = __import__(file_name,fromlist=True) if hasattr(pk,cls_name): obj = getattr(pk,cls_name)() response[k] = obj.execute(hostname) return response
from conf import setting class BasePlugin(object): def __init__(self): mode_list = ['Agent','Salt','SSH'] self.mode = setting.MODE if self.mode not in mode_list: raise Exception("请选择正确的管理模式") def shell_cmd(self,cmd): if self.mode == "SSH": ret = self.ssh(cmd) elif self.mode == "Salt": ret = self.salt(cmd) else: ret = self.agent(cmd) return ret def execute(self,hostname=None): self.hostname = hostname #windows:systeminfo详细信息 ver版本号 linux:cat /etc/issue | grep Linux sys_ver = self.shell_cmd("ver") pat = "command not found" if pat in sys_ver: #是linux...还是继续判断吧,应该不需要了 sys_ver = self.shell_cmd("head -n 1 /etc/issue") if "Linux" in sys_ver: return self.linux() elif "Windows" in sys_ver: return self.windows() else: raise Exception("只支持linux和windows平台") def linux(self): raise Exception("请重载linux函数") def windows(self): raise Exception("请实现windows方法") def write(self,output): import os with open(os.path.join(setting.BASEDIR,'file',"test.txt"),"w") as fp: fp.write(output) def agent(self,cmd): import subprocess try: ret = subprocess.getoutput(cmd) except AttributeError: sub = subprocess.Popen(args=cmd,shell=True,stdout=subprocess.PIPE) sub.wait() ret = sub.stdout.read() return ret def salt(self,cmd): import subprocess result = subprocess.getoutput("Salt '主机名' cmd.run '"+cmd+"'") return result # import salt.client # local = salt.client.LocalClient() # result = local.cmd(self.hostname,'cmd.run',[cmd]) # return result[self.hostname] def ssh(self,cmd): import paramiko ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) private_key = paramiko.RSAKey.from_private_key_file("文件:其中保存了私钥,用于解密") ssh.connect(hostname="远程主机名",port="远程端口",username="用户名",pkey="私钥private_key") stdin,stdout,stderr = ssh.exec_command(cmd) result = stdout.read() ssh.close() return result
from .base import BasePlugin import re from conf import setting import traceback from lib.response import BaseResponse class BasicPlugin(BasePlugin): def __init__(self): super(BasicPlugin, self).__init__() self.Basic_data = {} # 存放我们获取的数据 def windows(self): response = BaseResponse() try: # 获取主机名 output = self.shell_cmd("hostname") self.Basic_data['hostname'] = output output = self.shell_cmd("ver") #获取操作平台 ret = re.search("(.*)\s*\[",output) if ret: self.Basic_data['os_platform'] = ret.group(1) # 获取系统版本 ret = re.search("\[(.*)\]",output) if ret: self.Basic_data['os_version'] = ret.group(1) response.data = self.Basic_data except Exception as e: msg = "%s window memory plugins error: %s" response.status = False response.error = msg % (self.hostname, traceback.format_exc()) # traceback.format_exc()返回前面错误的信息 return response def linux(self): # 获取返回的字符串 output = self.shell_cmd("查看硬盘") # 进行正则匹配,放入Mem_data中 return self.Basic_data
from .base import BasePlugin from conf import setting import re,traceback from lib.response import BaseResponse class DiskPlugin(BasePlugin): def __init__(self): super(DiskPlugin, self).__init__() self.Disk_data = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'} def windows(self): response = BaseResponse() try: if setting.TEST_MODE == True: import os with open(os.path.join(setting.BASEDIR,'file','disk.out'),"r") as fp: output = fp.read() else: # 获取返回的字符串 output = self.shell_cmd("wmic logicaldisk") response.data = self.parse(output) except Exception as e: msg = "%s window disk plugin error: %s" response.status = False response.error = msg % (self.hostname,traceback.format_exc()) #traceback.format_exc()返回前面错误的信息 return response def linux(self): # 获取返回的字符串 output = self.shell_cmd("查看硬盘") # 进行正则匹配,放入Mem_data中 return self.Disk_data def parse(self,content): response = {} result = [] #模拟多台 使用4个\n分割 for row_line in content.split("\n\n\n\n"): result.append(row_line) for item in result: temp_dict = {} for row in item.split('\n'): if not row.strip(): continue if len(row.split(":")) != 2: continue key,val = row.split(":") name = self.patter_match(key) if name: if key == 'Raw Size': val = re.search("(\d+\.\d+)",val.strip()) if not val: val = 0 else: val = val.group(1) temp_dict[name] = val if temp_dict: response[temp_dict['slot']] = temp_dict return response def patter_match(self,key): for k,v in self.Disk_data.items(): if key.startswith(k): return v return False
from .base import BasePlugin from conf import setting import traceback from lib.response import BaseResponse class MemPlugin(BasePlugin): def __init__(self): super(MemPlugin, self).__init__() self.Mem_data = { 'Size': 'capacity', 'Locator': 'slot', 'Type': 'model', 'Speed': 'speed', 'Manufacturer': 'manufacturer', 'Serial Number': 'sn', } def windows(self): response = BaseResponse() try: if setting.TEST_MODE == True: import os with open(os.path.join(setting.BASEDIR, 'file', 'memory.out'), "r") as fp: output = fp.read() else: # 获取返回的字符串 output = self.shell_cmd("wmic memorychip") response.data = self.parse(output) except Exception as e: msg = "%s window memory plugins error: %s" response.status = False response.error = msg % (self.hostname, traceback.format_exc()) # traceback.format_exc()返回前面错误的信息 return response def linux(self): # 获取返回的字符串 output = self.shell_cmd("查看内存") # 进行正则匹配,放入Mem_data中 return self.Mem_data def parse(self,content): response = {} result = [] #模拟多台 使用4个\n分割 for row_line in content.split("Memory Device"): result.append(row_line) for item in result: for row in item.split('\n\t'): if not row.strip(): continue if len(row.split(":")) != 2: continue key,val = row.split(":") name = self.patter_match(key) if name: response[name] = val return response def patter_match(self,key): for k,v in self.Mem_data.items(): if key.startswith(k): return v return False
file文件目录(含有测试的文件,文件包含各种命令下的数据)
Enclosure Device ID: 32 Slot Number: 0 Drive's postion: DiskGroup: 0, Span: 0, Arm: 0 Enclosure position: 0 Device Id: 0 WWN: 5000C5007272C288 Sequence Number: 2 Media Error Count: 0 Other Error Count: 0 Predictive Failure Count: 0 Last Predictive Failure Event Seq Number: 0 PD Type: SAS Raw Size: 279.396 GB [0x22ecb25c Sectors] Non Coerced Size: 278.896 GB [0x22dcb25c Sectors] Coerced Size: 278.875 GB [0x22dc0000 Sectors] Firmware state: Online, Spun Up Device Firmware Level: LS08 Shield Counter: 0 Successful diagnostics completion on : N/A SAS Address(0): 0x5000c5007272c289 SAS Address(1): 0x0 Connected Port Number: 0(path0) Inquiry Data: SEAGATE ST300MM0006 LS08S0K2B5NV FDE Enable: Disable Secured: Unsecured Locked: Unlocked Needs EKM Attention: No Foreign State: None Device Speed: 6.0Gb/s Link Speed: 6.0Gb/s Media Type: Hard Disk Device Drive Temperature :29C (84.20 F) PI Eligibility: No Drive is formatted for PI information: No PI: No PI Drive's write cache : Disabled Port-0 : Port status: Active Port's Linkspeed: 6.0Gb/s Port-1 : Port status: Active Port's Linkspeed: Unknown Drive has flagged a S.M.A.R.T alert : No Enclosure Device ID: 32 Slot Number: 1 Drive's postion: DiskGroup: 0, Span: 0, Arm: 1 Enclosure position: 0 Device Id: 1 WWN: 5000C5007272DE74 Sequence Number: 2 Media Error Count: 0 Other Error Count: 0 Predictive Failure Count: 0 Last Predictive Failure Event Seq Number: 0 PD Type: SAS Raw Size: 279.396 GB [0x22ecb25c Sectors] Non Coerced Size: 278.896 GB [0x22dcb25c Sectors] Coerced Size: 278.875 GB [0x22dc0000 Sectors] Firmware state: Online, Spun Up Device Firmware Level: LS08 Shield Counter: 0 Successful diagnostics completion on : N/A SAS Address(0): 0x5000c5007272de75 SAS Address(1): 0x0 Connected Port Number: 0(path0) Inquiry Data: SEAGATE ST300MM0006 LS08S0K2B5AH FDE Enable: Disable Secured: Unsecured Locked: Unlocked Needs EKM Attention: No Foreign State: None Device Speed: 6.0Gb/s Link Speed: 6.0Gb/s Media Type: Hard Disk Device Drive Temperature :29C (84.20 F) PI Eligibility: No Drive is formatted for PI information: No PI: No PI Drive's write cache : Disabled Port-0 : Port status: Active Port's Linkspeed: 6.0Gb/s Port-1 : Port status: Active Port's Linkspeed: Unknown Drive has flagged a S.M.A.R.T alert : No Enclosure Device ID: 32 Slot Number: 2 Drive's postion: DiskGroup: 1, Span: 0, Arm: 0 Enclosure position: 0 Device Id: 2 WWN: 50025388A075B731 Sequence Number: 2 Media Error Count: 0 Other Error Count: 1158 Predictive Failure Count: 0 Last Predictive Failure Event Seq Number: 0 PD Type: SATA Raw Size: 476.939 GB [0x3b9e12b0 Sectors] Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors] Coerced Size: 476.375 GB [0x3b8c0000 Sectors] Firmware state: Online, Spun Up Device Firmware Level: 1B6Q Shield Counter: 0 Successful diagnostics completion on : N/A SAS Address(0): 0x500056b37789abee Connected Port Number: 0(path0) Inquiry Data: S1SZNSAFA01085L Samsung SSD 850 PRO 512GB EXM01B6Q FDE Enable: Disable Secured: Unsecured Locked: Unlocked Needs EKM Attention: No Foreign State: None Device Speed: 6.0Gb/s Link Speed: 6.0Gb/s Media Type: Solid State Device Drive: Not Certified Drive Temperature :25C (77.00 F) PI Eligibility: No Drive is formatted for PI information: No PI: No PI Drive's write cache : Disabled Drive's NCQ setting : Disabled Port-0 : Port status: Active Port's Linkspeed: 6.0Gb/s Drive has flagged a S.M.A.R.T alert : No Enclosure Device ID: 32 Slot Number: 3 Drive's postion: DiskGroup: 1, Span: 0, Arm: 1 Enclosure position: 0 Device Id: 3 WWN: 50025385A02A074F Sequence Number: 2 Media Error Count: 0 Other Error Count: 0 Predictive Failure Count: 0 Last Predictive Failure Event Seq Number: 0 PD Type: SATA Raw Size: 476.939 GB [0x3b9e12b0 Sectors] Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors] Coerced Size: 476.375 GB [0x3b8c0000 Sectors] Firmware state: Online, Spun Up Device Firmware Level: 6B0Q Shield Counter: 0 Successful diagnostics completion on : N/A SAS Address(0): 0x500056b37789abef Connected Port Number: 0(path0) Inquiry Data: S1AXNSAF912433K Samsung SSD 840 PRO Series DXM06B0Q FDE Enable: Disable Secured: Unsecured Locked: Unlocked Needs EKM Attention: No Foreign State: None Device Speed: 6.0Gb/s Link Speed: 6.0Gb/s Media Type: Solid State Device Drive: Not Certified Drive Temperature :28C (82.40 F) PI Eligibility: No Drive is formatted for PI information: No PI: No PI Drive's write cache : Disabled Drive's NCQ setting : Disabled Port-0 : Port status: Active Port's Linkspeed: 6.0Gb/s Drive has flagged a S.M.A.R.T alert : No Enclosure Device ID: 32 Slot Number: 4 Drive's postion: DiskGroup: 1, Span: 1, Arm: 0 Enclosure position: 0 Device Id: 4 WWN: 50025385A01FD838 Sequence Number: 2 Media Error Count: 0 Other Error Count: 0 Predictive Failure Count: 0 Last Predictive Failure Event Seq Number: 0 PD Type: SATA Raw Size: 476.939 GB [0x3b9e12b0 Sectors] Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors] Coerced Size: 476.375 GB [0x3b8c0000 Sectors] Firmware state: Online, Spun Up Device Firmware Level: 5B0Q Shield Counter: 0 Successful diagnostics completion on : N/A SAS Address(0): 0x500056b37789abf0 Connected Port Number: 0(path0) Inquiry Data: S1AXNSAF303909M Samsung SSD 840 PRO Series DXM05B0Q FDE Enable: Disable Secured: Unsecured Locked: Unlocked Needs EKM Attention: No Foreign State: None Device Speed: 6.0Gb/s Link Speed: 6.0Gb/s Media Type: Solid State Device Drive: Not Certified Drive Temperature :27C (80.60 F) PI Eligibility: No Drive is formatted for PI information: No PI: No PI Drive's write cache : Disabled Drive's NCQ setting : Disabled Port-0 : Port status: Active Port's Linkspeed: 6.0Gb/s Drive has flagged a S.M.A.R.T alert : No Enclosure Device ID: 32 Slot Number: 5 Drive's postion: DiskGroup: 1, Span: 1, Arm: 1 Enclosure position: 0 Device Id: 5 WWN: 50025385A02AB5C9 Sequence Number: 2 Media Error Count: 0 Other Error Count: 0 Predictive Failure Count: 0 Last Predictive Failure Event Seq Number: 0 PD Type: SATA Raw Size: 476.939 GB [0x3b9e12b0 Sectors] Non Coerced Size: 476.439 GB [0x3b8e12b0 Sectors] Coerced Size: 476.375 GB [0x3b8c0000 Sectors] Firmware state: Online, Spun Up Device Firmware Level: 6B0Q Shield Counter: 0 Successful diagnostics completion on : N/A SAS Address(0): 0x500056b37789abf1 Connected Port Number: 0(path0) Inquiry Data: S1AXNSAFB00549A Samsung SSD 840 PRO Series DXM06B0Q FDE Enable: Disable Secured: Unsecured Locked: Unlocked Needs EKM Attention: No Foreign State: None Device Speed: 6.0Gb/s Link Speed: 6.0Gb/s Media Type: Solid State Device Drive: Not Certified Drive Temperature :28C (82.40 F) PI Eligibility: No Drive is formatted for PI information: No PI: No PI Drive's write cache : Disabled Drive's NCQ setting : Disabled Port-0 : Port status: Active Port's Linkspeed: 6.0Gb/s Drive has flagged a S.M.A.R.T alert : No Exit Code: 0x00
Memory Device Total Width: 32 bits Data Width: 32 bits Size: 1024 MB Form Factor: DIMM Set: None Locator: DIMM #0 Bank Locator: BANK #0 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown Memory Device Total Width: 32 bits Data Width: 32 bits Size: No Module Installed Form Factor: DIMM Set: None Locator: DIMM #1 Bank Locator: BANK #1 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown Memory Device Total Width: 32 bits Data Width: 32 bits Size: No Module Installed Form Factor: DIMM Set: None Locator: DIMM #2 Bank Locator: BANK #2 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown Memory Device Total Width: 32 bits Data Width: 32 bits Size: No Module Installed Form Factor: DIMM Set: None Locator: DIMM #3 Bank Locator: BANK #3 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown Memory Device Total Width: 32 bits Data Width: 32 bits Size: No Module Installed Form Factor: DIMM Set: None Locator: DIMM #4 Bank Locator: BANK #4 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown Memory Device Total Width: 32 bits Data Width: 32 bits Size: No Module Installed Form Factor: DIMM Set: None Locator: DIMM #5 Bank Locator: BANK #5 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown Memory Device Total Width: 32 bits Data Width: 32 bits Size: No Module Installed Form Factor: DIMM Set: None Locator: DIMM #6 Bank Locator: BANK #6 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown Memory Device Total Width: 32 bits Data Width: 32 bits Size: No Module Installed Form Factor: DIMM Set: None Locator: DIMM #7 Bank Locator: BANK #7 Type: DRAM Type Detail: EDO Speed: 667 MHz Manufacturer: Not Specified Serial Number: Not Specified Asset Tag: Not Specified Part Number: Not Specified Rank: Unknown
.....
三:代码实现(API接口Django实现)
API目录
from django.views import View from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.http import HttpResponse,JsonResponse from repository import models from API import config from utils import auth import importlib import json class AssetView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(AssetView, self).dispatch(request, *args, **kwargs) @method_decorator(auth.api_auth) def get(self,request,*args,**kwargs): pass @method_decorator(auth.api_auth) def post(self,request,*args,**kwargs): ''' 获取传递的数据,进行添加,修改 :param request: :param args: :param kwargs: :return:1000成功,1001,接口授权失败,1002数据库中资产不存在 ''' #获取所有信息 # print(request.body.decode('utf-8')) #带转义符的字符串 server_info = json.loads(request.body.decode("utf-8")) # print(server_info) #字符串 server_info = json.loads(server_info) #获取主机名 hostname = server_info['basic']['data']['hostname'] ret = {"code":1000,"message":'[%s]更新完成'%hostname} #根据主机名去数据库中获取相关信息 server_obj = models.Server.objects.filter(hostname=hostname).select_related('asset').first() if not server_obj: ret['code'] = 1002 ret['message'] = '[%s]资产不存在' return JsonResponse(ret) for k,v in config.PLUGINS_DICT.items(): module_path,cls_name = v.rsplit('.',1) cls = getattr(importlib.import_module(module_path),cls_name) response = cls.process(server_obj,server_info,None) if not response.status: ret['code'] = 1003 ret['message'] = "[%s]资产更新异常" % hostname if hasattr(cls,'update_last_time'): cls.update_last_time(server_obj,None) return JsonResponse(ret)
二级目录service
from repository import models import traceback,datetime from utils.response import BaseResponse from utils.agorithm import * class HandleBasic(object): @staticmethod def process(server_obj,server_info,user_obj): response = BaseResponse() try: log_list = [] main_board = server_info['basic']['data'] if main_board['os_platform'] != server_obj.os_platform: log_list.append("系统由%s变更为%s"%(server_obj.os_platform,main_board['os_platform'])) server_obj.os_platform = main_board['os_platform'] if main_board['os_version'] != server_obj.os_version: log_list.append("系统由%s变更为%s"%(server_obj.os_version,main_board['os_version'])) server_obj.os_version = main_board['os_version'] server_obj.save() if log_list: models.AssetRecord.objects.create(asset_obj=server_obj.asset,creator=user_obj,content=';'.join(log_list)) except Exception as e: response.status = False models.ErrorLog.objects.create(asset_obj=server_obj.asset,title="basic-run", content=traceback.format_exc()) return response @staticmethod def update_last_time(server_obj,user_obj): response = BaseResponse() try: current_date = datetime.date.today() server_obj.asset.latest_date = current_date server_obj.asset.save() models.AssetRecord.objects.create(asset_obj=server_obj.asset,creator=user_obj,content="资产汇报") except Exception as e: response.status = False models.ErrorLog.objects.create(asset_obj=server_obj.asset,title="basic-run", content=traceback.format_exc()) return response class HandleDisk(object): @staticmethod def process(server_obj,server_info,user_obj): response = BaseResponse() try: disk_info = server_info['disk'] if not disk_info['status']: response.status = False models.ErrorLog.objects.create( asset_obj=server_obj.asset, title="disk-plugins", content=disk_info['error'] ) return response client_disk_dict = disk_info['data'] #获取服务器下的所有硬盘设备 disk_obj_list = models.Disk.objects.filter(server_obj=server_obj) #获取插槽 disk_slots = map(lambda x:x, (item.slot for item in disk_obj_list)) update_list = get_intersection(set(client_disk_dict.keys()),set(disk_slots)) add_list = get_exclude(client_disk_dict.keys(),update_list) del_list = get_exclude(disk_slots,update_list) HandleDisk._add_disk(add_list,client_disk_dict,server_obj,user_obj) HandleDisk._update_disk(update_list,disk_obj_list,client_disk_dict,server_obj,user_obj) HandleDisk._del_disk(del_list,disk_obj_list,server_obj,user_obj) except Exception as e: response.status = False models.ErrorLog.objects.create( asset_obj=server_obj.asset, title="disk-plugins", content=traceback.format_exc() ) return response @staticmethod def _add_disk(add_list,client_disk_dict,server_obj,user_obj): for item in add_list: cur_disk_dict = client_disk_dict[item] log_str = "[新增硬盘]插槽位{slot};容量为{capacity};硬盘类型为{pd_type};型号为{model}".format(**cur_disk_dict) cur_disk_dict['server_obj'] = server_obj models.Disk.objects.create(**cur_disk_dict) models.AssetRecord.objects.create( asset_obj=server_obj.asset,creator=user_obj,content=log_str ) @staticmethod def _del_disk(del_lsit,disk_objs,server_obj,user_obj): for item in disk_objs: if item.slot in del_lsit: log_str = "[移除硬盘]插槽位{slot};容量为{capacity};硬盘类型为{pd_type};型号为{model}".format(item.__dict__) item.delete() models.AssetRecord.objects.create( asset_obj = server_obj.asset, creator = user_obj, content = log_str ) @staticmethod def _update_disk(update_list,disk_objs,client_disk_dict,server_obj,user_obj): for item in disk_objs: if item.slot in update_list: log_list = [] new_model = client_disk_dict[item.slot]['model'] if item.model != new_model: log_list.append("[更新硬盘]插槽为%s:型号由%s变更为%s"%(item.slot,item.model,new_model)) item.model = new_model new_capacity = client_disk_dict[item.slot]['capacity'] new_capacity = float(new_capacity) if new_capacity != item.capacity: log_list.append("[更新硬盘]插槽为%s:容量由%sGB变更为%sGB"%(item.slot,item.capacity,new_capacity)) item.capacity = new_capacity new_pd_type = client_disk_dict[item.slot]['pd_type'] if item.pd_type != new_pd_type: log_list.append("[更新硬盘]插槽为%s:类型由%s变更为%s"%(item.slot,item.pd_type,new_pd_type)) item.pd_type = new_pd_type item.save() if log_list: models.AssetRecord.objects.create( asset=server_obj.asset, creator=user_obj, content=';'.join(log_list) )
utils工具目录
import hashlib,time,redis from CMDBAPI import settings from django.http import JsonResponse def api_auth_method(request): auth_key = settings.KEY auth_name = settings.AUTH_KEY_NAME auth_data = request.META['HTTP_'+auth_name.upper()] auth_val,auth_time = str(auth_data).split('|') #1:时间上的验证 if float(auth_time) + 10 < time.time(): return False #2:数据是否过期,先从redis获取数据{auth_val:auth_time} rd = redis.Redis(host="localhost",port=6379) r_time = rd.get(auth_val) if r_time: return False #3:验证数据是否被修改 ha = hashlib.md5(auth_key.encode("utf-8")) ha.update(("%s|%s" % (auth_key, auth_time)).encode("utf-8")) encryption = ha.hexdigest() if encryption != auth_val: return False rd.set(auth_val,auth_time) return True def api_auth(func): def inner(request,*args,**kwargs): if not api_auth_method(request): return JsonResponse({"code":1001,"message":"API授权失败"},json_dumps_params={'ensure_ascii': False}) return func(request,*args,**kwargs) return inner
def get_intersection(*args): ''' 获取集合的并集 :param args: set集合 :return: 并集列表 ''' base = args[0] result = base.intersection(*args) return list(result) def get_exclude(total,part): ''' 获取差集 :param total: :param part: :return: ''' result = [] for item in total: if item in part: pass else: result.append(item) return result
四:数据库实现
class UserProfile(models.Model): ''' 用户信息 ''' name = models.CharField("姓名",max_length=32) email = models.EmailField("邮箱") phone = models.CharField("座机",max_length=32) mobile = models.CharField("手机",max_length=32) class Meta: verbose_name_plural = "用户表" def __str__(self): return self.name class AdminInfo(models.Model): ''' 用户登录管理信息 与用户信息表时一对一关系 ''' user_info = models.OneToOneField("UserProfile") username = models.CharField("用户名",max_length=64) password = models.CharField("密码",max_length=64) class Meta: verbose_name_plural = "管理员表" def __str__(self): return self.user_info.name class UserGroup(models.Model): ''' 用户组:业务是按照用户组分配,而不是某个人 每个人可以有多个组 每个组有多个人 ''' name = models.CharField(max_length=32,unique=True) users = models.ManyToManyField("UserProfile") class Meta: verbose_name_plural = "用户组" def __str__(self): return self.name class BusinessUnit(models.Model): ''' 业务线 ''' name = models.CharField("业务线",max_length=64,unique=True) contact = models.ForeignKey("UserGroup",verbose_name="业务联系人",related_name="c") #多个人,所以按组来分配 manager = models.ForeignKey("UserGroup",verbose_name="系统管理员",related_name="m") class Meta: verbose_name_plural = "业务线" def __str__(self): return self.name class IDC(models.Model): ''' 机房信息:楼层和机房 ''' name = models.CharField("机房",max_length=32) floor = models.IntegerField("楼层",default=1) class Meta: verbose_name_plural = "机房表" def __str__(self): return self.name class Tag(models.Model): ''' 资产标签:以前是做什么的,web服务器....,一个资产可以有多个标签,一个标签可以有端个资产 ''' name = models.CharField("标签",max_length=32,unique=True) class Meta: verbose_name_plural = "标签表" def __str__(self): return self.name class Asset(models.Model): ''' 资产信息表,所有资产公共信息(交换机,服务器,防火墙等) ''' device_type_choices = ( (1,"服务器"), (2,"交换机"), (3,"防火墙"), ) device_status_choices = ( (1,"上架"), (2,"在线"), (3,"离线"), (4,"下架") ) device_type_id = models.IntegerField(choices=device_type_choices,default=1) device_status_id = models.IntegerField(choices=device_status_choices,default=1) cabinet_num = models.CharField("机柜号",max_length=32,null=True,blank=True) cabinet_order = models.CharField("机柜中的序号",max_length=32,null=True,blank=True) idc = models.ForeignKey("IDC",verbose_name="IDC机房",null=True,blank=True) business_unit = models.ForeignKey("BusinessUnit",verbose_name="属于的业务线",null=True,blank=True) tag = models.ManyToManyField("Tag") latest_date = models.DateField(null=True) create_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "资产表" def __str__(self): return "%s-%s-%s" % (self.idc.name,self.cabinet_num,self.cabinet_order) class Server(models.Model): ''' 服务器信息:服务器和资产是一对一关系,一个资产下有一个服务器,或者交换机,或者... ''' asset = models.OneToOneField('Asset') hostname = models.CharField(max_length=128,unique=True) sn = models.CharField('SN号',max_length=64,db_index=True) manafacturer = models.CharField("制造商",max_length=64,null=True,blank=True) model = models.CharField("型号",max_length=64,null=True,blank=True) manage_ip = models.GenericIPAddressField("管理IP",null=True,blank=True) os_platform = models.CharField("系统",max_length=32,null=True,blank=True) os_version = models.CharField("系统版本",max_length=32,null=True,blank=True) cpu_count = models.IntegerField("CPU个数",null=True,blank=True) cpu_physical = models.IntegerField("CPU物理个数",null=True,blank=True) cpu_model = models.CharField("CPU型号",max_length=128,null=True,blank=True) create_at = models.DateTimeField(auto_now_add=True,blank=True) class Meta: verbose_name_plural = "服务器列表" def __str__(self): return self.hostname class NetworkDevice(models.Model): ''' 其他网络设备表,交换机... ''' asset = models.OneToOneField("asset") management_ip = models.CharField("管理IP",max_length=64,blank=True,null=True) vlan_ip = models.CharField("VlanIP",max_length=64,blank=True,null=True) intranet_ip = models.CharField('内网IP', max_length=128, blank=True, null=True) sn = models.CharField('SN号', max_length=64, unique=True) manufacture = models.CharField(verbose_name=u'制造商', max_length=128, null=True, blank=True) model = models.CharField('型号', max_length=128, null=True, blank=True) port_num = models.SmallIntegerField('端口个数', null=True, blank=True) device_detail = models.CharField('设置详细配置', max_length=255, null=True, blank=True) class Meta: verbose_name_plural = "网络设备" class Disk(models.Model): ''' 硬盘信息 ''' slot = models.CharField("插槽位",max_length=8) model = models.CharField("硬盘类型",max_length=32) capacity = models.FloatField("磁盘容量GB") pd_type = models.CharField("磁盘类型",max_length=32) server_obj = models.ForeignKey("Server",related_name='disk') class Meta: verbose_name_plural = "硬盘表" def __str__(self): return self.slot class NIC(models.Model): ''' 网卡信息 ''' name = models.CharField("网卡名称",max_length=128) hwaddr = models.CharField("网卡mac地址",max_length=64) netmask = models.CharField(max_length=64) idaddrs = models.CharField("IP地址",max_length=256) up = models.BooleanField(default=False) server_obj = models.ForeignKey("Server",related_name="nic") class Meta: verbose_name_plural = "网卡表" def __str__(self): return self.name class Memory(models.Model): ''' 内存信息表 ''' slot = models.CharField("插槽位",max_length=32) manafacturer = models.CharField("制造商",max_length=32,null=True,blank=True) model = models.CharField("型号",max_length=64) capacity = models.FloatField("容量",null=True,blank=True) sn = models.CharField("内存SN号",max_length=64,null=True,blank=True) speed = models.CharField("速度",max_length=16,null=True,blank=True) server_obj = models.ForeignKey("Server",related_name="memory") class Meta: verbose_name_plural = "内存表" def __str__(self): return self.slot class AssetRecord(models.Model): ''' 资产变更记录 ''' asset_obj = models.ForeignKey("Asset",related_name='ar') content = models.TextField(null=True) creator = models.ForeignKey("UserProfile",null=True,blank=True) #creator为空,代表是资产汇报的数据 create_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "资产记录表" def __str__(self): return "%s-%s-%s" % (self.asset_obj.idc.name, self.asset_obj.cabinet_num, self.asset_obj.cabinet_order) class ErrorLog(models.Model): ''' 错误日志 ''' asset_obj = models.ForeignKey("Asset",null=True,blank=True) title = models.CharField(max_length=16) content = models.TextField() create_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "错误日志表" def __str__(self): return self.title
五:后台管理(实现动态编辑数据)
from django.shortcuts import render,HttpResponse from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.db import transaction from django.views import View from repository import models import json,traceback # Create your views here. class Assert(View): def get(self,request,*args,**kwargs): return render(request,"asset.html") class AssertJson(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(AssertJson, self).dispatch(request,*args,**kwargs) def get(self,request,*args,**kwargs): config_list = [ { 'field': None, 'display': True, 'title': "选项", 'text': {'content': "<input type='checkbox'/>", 'kwargs': {}}, 'attrs': {} }, { 'field': "id", 'display': False, 'title': "ID", 'text': {}, 'attrs': {} }, { 'field':"cabinet_num", 'display':True, 'title':"机柜号", 'text': {"content":"{n}","kwargs":{'n':"@cabinet_num"}}, 'attrs': {'name':'cabinet_num','origin':'@cabinet_num','edit-able': True, 'edit-type': 'input'} }, { 'field': "cabinet_order", 'display': True, 'title': "机柜位置", 'text':{"content":"{n}","kwargs":{'n':"@cabinet_order"}}, 'attrs': {'name':'cabinet_order','origin':'@cabinet_order','edit-able': True, 'edit-type': 'input'} }, { 'field': "device_type_id", 'display': True, 'title': "资产类型", 'text': {"content": "{n}", "kwargs": {'n': "@@device_type_choices"}}, 'attrs': {'name':'device_type_id','origin':'@device_type_id','edit-able': True, 'edit-type': 'select','global-name':'device_type_choices'} }, { 'field': "device_status_id", 'display': True, 'title': "设备状态", 'text': {"content": "{n}", "kwargs": {'n': "@@device_status_choices"}}, 'attrs': {'name':'device_status_id','origin':'@device_status_id','edit-able': True, 'edit-type': 'select','global-name':'device_status_choices'} }, { 'field': "idc__id", 'display': False, 'title': "IDC", 'text': {}, 'attrs': {} }, { 'field': "idc__name", 'display': True, 'title': "IDC机房", 'text': {"content": "{n}", "kwargs": {'n': "@idc__name"}}, 'attrs': {'name': 'idc_id', 'origin': '@idc__id', 'edit-able': True,'edit-type': 'select', 'global-name': 'idc_choices'} }, { 'field': None, 'display': True, 'title': "操作", "text":{"content":"<a href='/asset.html/{m}'>{n}</a>","kwargs":{'n':"点击查看","m":"@id"}}, 'attrs': {} }, ] #获取数据库中的数据 req_list = [] for item in config_list: if item['field']: req_list.append(item['field']) content = models.Asset.objects.all().values(*req_list) content = list(content) #获取类型 global_data = { 'device_type_choices':models.Asset.device_type_choices, 'device_status_choices':models.Asset.device_status_choices, 'idc_choices':list(models.IDC.objects.values_list('id','name')), } response = { 'table_config':config_list, 'content':content, 'global_data':global_data, } return HttpResponse(json.dumps(response)) def put(self,request,*args,**kwargs): ret = { 'status':True, 'message':None, } data = json.loads(request.body.decode("utf-8")) try: with transaction.atomic(): AssetObj = models.Asset.objects for item in data: AssetObj.filter(id=item['id']).update(**item) except Exception as e: ret['status'] = False ret['message'] = traceback.format_exc() return HttpResponse(json.dumps(ret))
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/statics/plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css"> </head> <body> <div style="width: 1000px;margin: 0 auto;"> <h1>资源列表</h1> <div class="btn-group" role="group"> <button id="idCheckAll" type="button" class="btn btn-default">全选</button> <button id="idReverseAll" type="button" class="btn btn-default">反选</button> <button id="idCancelAll" type="button" class="btn btn-default">取消</button> <button id="idEditMode" type="button" class="btn btn-default">进入编辑模式</button> <button type="button" class="btn btn-default">批量删除</button> <button id="idSave" type="button" class="btn btn-default">保存</button> <button type="button" class="btn btn-default">添加</button> </div> <table class="table table-bordered"> <thead id="table_th"></thead> <tbody id="table_tb"></tbody> </table> </div> </body> </html> <script src="/statics/js/jquery.js"></script> <script src="/statics/js/nbplugin.js"></script> <script> $(function(){ $.NB("/Backend/asset-join.html"); }) </script>
(function(){ var requestUrl = null; /*自定义字符串格式化函数*/ String.prototype.format= function(kwargs){ var ret=this.replace(/\{(\w+)\}/g,function(g,g1){ return kwargs[g1]; }) return ret } function bindSave(){ $("#idSave").click(function(){ var postList = []; var flag = false; //判断是否有数据被修改 //先找到修改过的行 $("#table_tb").find("tr[has-edit='true']").each(function(){ var temp = {}; var id = $(this).attr("row-id"); $(this).find("td[edit-able='true']").each(function(){ var origin = $(this).attr("origin"); var newVal = $(this).attr("new-val"); if(newVal && newVal != origin){ var name = $(this).attr("name"); temp[name] = newVal; flag = true } }); //如果被修改过,添加到postList中 if(flag){ temp['id'] = id; postList.push(temp); } }); if(flag){ $.ajax({ 'url':requestUrl, 'type':'PUT', //put表示更新数据 'data':JSON.stringify(postList), 'dataType':"json", success:function(response){ if(response.status){ alert("更新成功"); init(); }else{ alert("更新失败:"+response.message); } } }) } }) } function bindReverse(){ $("#idReverseAll").click(function(){ var editing = $("#idEditMode").hasClass("btn-warning"); $("#table_tb").find(":checkbox").each(function(){ if(editing){ //若是点击了编辑模式,则是需要进入或退出编辑模式 if($(this).prop("checked")){ $tr = $(this).parents("tr"); trOutEditMode($tr); $(this).prop("checked",false); }else{ $tr = $(this).parents("tr"); trIntoEditMode($tr); $(this).prop("checked",true); } }else{ //只需要修改复选框按钮状态 $(this).prop("checked",!$(this).prop("checked")); } }) }) } function bindCancelAll(){ $("#idCancelAll").click(function(){ var editing = $("#idEditMode").hasClass("btn-warning"); $("#table_tb").find(":checkbox").each(function(){ if(editing){ if($(this).prop("checked")){ $tr = $(this).parents("tr"); trOutEditMode($tr); $(this).prop("checked",false); } }else{ if($(this).prop("checked")){ $(this).prop("checked",false); } } }); }); } function bindCheckAll() { $("#idCheckAll").click(function(){ var editing = $("#idEditMode").hasClass("btn-warning"); if(editing){ $("#table_tb").find(":checkbox").each(function(){ if($(this).prop("checked")){ //无操作 }else{ $tr = $(this).parents("tr"); trIntoEditMode($tr); $(this).prop("checked",true); } }) }else{ $("#table_tb").find(":checkbox").each(function(){ if(!$(this).prop("checked")){ $(this).prop("checked",true); } }) } }) } function bindCheckBox(){ $("#table_tb").on('click',':checkbox',function(){ if($("#idEditMode").hasClass("btn-warning")){ var ck = $(this).prop("checked"); $currentTr = $(this).parents("tr"); if(ck){ //进入编辑模式 trIntoEditMode($currentTr); }else{ //退出编辑模式 trOutEditMode($currentTr); } } }) } function trIntoEditMode($tr){ $tr.addClass("success"); //添加样式 $tr.attr("has-edit","true"); //代表进入了编辑模式,传递数据的时候会去看他 $tr.children().each(function(){ var edit_enable = $(this).attr("edit-able"); var edit_type = $(this).attr("edit-type"); if(edit_enable == "true"){ if(edit_type == "select"){ var global_name = $(this).attr("global-name"); var origin = $(this).attr("origin"); var tag = document.createElement('select'); tag.className = "form-control"; $.each(window[global_name],function(k,v){ var optag = document.createElement("option"); optag.setAttribute("value",v[0]); optag.innerHTML = v[1]; tag.append(optag); }); $(tag).val(origin); $(this).html(tag); }else if(edit_type == "input"){ var innerText = $(this).text(); var tag = document.createElement("input"); tag.className = "form-control"; tag.value = innerText; $(this).html(tag) } } }) } function trOutEditMode($tr) { $tr.removeClass("success"); //添加样式 $tr.children().each(function(){ var edit_enable = $(this).attr("edit-able"); var edit_type = $(this).attr("edit-type"); if(edit_enable == "true"){ if(edit_type == "select"){ var $select = $(this).children().first(); var newId = $select.val(); var newText = $select[0].selectedOptions[0].text; $(this).attr("new-val",newId); $(this).html(newText); }else if(edit_type == "input"){ var $input = $(this).children().first(); var inputValue = $input.val(); $(this).html(inputValue); $(this).attr("new-val",inputValue); } } }) } function bindEditMode(){ $("#idEditMode").click(function(){ var editstatus = $(this).hasClass("btn-warning"); if(!editstatus){ /*进入编辑模式*/ $(this).addClass("btn-warning"); $(this).text("退出编辑模式"); $("#table_tb").find(":checked").each(function(){ var $tr = $(this).parents("tr"); trIntoEditMode($tr); }) }else{ /*退出编辑模式*/ $(this).removeClass("btn-warning"); $(this).text("进入编辑模式"); $("#table_tb").find(":checked").each(function(){ var $tr = $(this).parents("tr"); trOutEditMode($tr); }) } }) } function init(){ $("#table_th").empty(); $("#table_tb").empty(); $.ajax({ url:requestUrl, type:"get", dataType:"json", success:function(response){ initGlobal(response.global_data); initHeader(response.table_config); intiContent(response.table_config,response.content); } }) } function initGlobal(gloable_data) { $.each(gloable_data,function(k,v){ window[k] = v }) } function initHeader(table_config){ var tr = document.createElement("tr"); $.each(table_config,function(k,v){ if(v.display){ var th = document.createElement("th"); th.innerHTML = v.title; tr.append(th); } }) $("#table_th").append(tr); } function intiContent(table_config,content){ $.each(content,function(k,item){ var tr = document.createElement("tr"); $.each(table_config,function(k1,tit){ if(tit.display){ var td = document.createElement("td"); /*生成文本信息*/ var kwargs = {}; /*将格式化的信息放在该对象中保存,之后我们生成文本信息的时候直接从这里面去取*/ $.each(tit.text.kwargs,function(k2,v2) { if(v2.substring(0,2) == "@@"){ var index = item[tit.field]; var global_name = v2.substring(2,v2.length); kwargs[k2] = getValueFromGlobalByID(global_name,index) }else if(v2[0] == "@"){ kwargs[k2] = item[v2.substring(1,v2.length)] }else{ kwargs[k2] = v2 } }); /*设置属性*/ $.each(tit.attrs,function(k3,v3){ if(v3[0] == "@"){ td.setAttribute(k3,item[v3.substring(1,v3.length)]) }else{ td.setAttribute(k3,v3) } }) /*内联代码*/ td.innerHTML = tit.text.content.format(kwargs); tr.append(td) } }) $(tr).attr("row-id",item['id']) $("#table_tb").append(tr) }) } function getValueFromGlobalByID(global_name,index){ var ret = null; $.each(window[global_name],function(k,v){ if(v[0] == index){ ret = v[1] return false } }) return ret } jQuery.extend({ 'NB':function (url){ requestUrl = url; init(); bindEditMode(); bindCheckBox(); bindCheckAll(); bindCancelAll(); bindReverse(); bindSave(); }, }) })()
六:CMDB项目总结
1. 四种采集资产的方式(掌握3种) 唯一标识的处理(选用主机名<人为定制规则>,因为其他的也是不具有唯一性,SN码在虚拟机上是一致的) 2. API API验证(时间验证,同一时间请求过滤,加密密匙)3次验证,(同加密cookie+时间限制+访问记录) 数据库表设计 3. 后台管理 js自定制插件(前端+后端配置),减少CURD操作
来源:https://www.cnblogs.com/ssyfj/p/9060367.html