一个简陋的批量操作zabbix监控项页面
个人博客地址
继上篇zabbix-api,决定简单写一个页面来完成zabbix-api的批量操作
使用django完成
目录如下
这个简陋的页面长这样,登录
长这样,主页,展示groups
长这样,展示groups中的所有agent类host
长这样,展示host中筛选过的items
最后添加items页面长这样,可以多选key,批量添加
代码部分
自己日常工作用,页面不重要,重要的是功能,因为写页面太tm耗时了
下面贴下代码
urls.py
from django.contrib import admin from django.urls import path from DarkZabbix import views urlpatterns = [ path('admin/', admin.site.urls), path('', views.zabbix), path('login.html/', views.login), path('zabbix.html/', views.zabbix), path('zabbix_host.html/', views.zabbix_host), path('zabbix_items.html/', views.zabbix_items), path('additems.html/', views.add_items), path('keylist.html/', views.key_list), path('delitem.html/', views.del_items), ]
templates里只用两个html页面,一个用于登录,一个用于展示和操作
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>登录</title> <style> .input{width: 300px; height: 20px;margin-top: 10px;} </style> </head> <body style="background-color: royalblue;"> <div style="position: absolute;top: 50%;left: 50%;transform: translate(-50%,-50%);"> <form action="/login.html/" method="POST"> {% csrf_token %} <h1 style="text-align: center; color: seashell;">Dark-zabbix</h1> <input class="input" name="username" type="text" placeholder=username> <br> <input class="input" name="passwd" type="password" placeholder=passwd> <br> <button style="width: 300px;height: 30px; margin-top: 10px;" >登录</button> <span>{{error}}</span> </form> </div> </body> </html>
zabbix.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>index</title> <link href="/static/index.css" rel='stylesheet' type="text/css"> <script src="/static/jquery-3.6.0.js"></script> <script src="/static/jquery.cookie.js"></script> <script src="/static/index.js"></script> </head> <body> {% csrf_token %} <div class="hidden block"></div> <div class="top"> <div class="top-left"> DarkZabbix </div> <div class="top-right"> <div>用户:{{request.user}}</div> </div> </div> <div class="left"> {%for hostgroup in groupli%} <a class="leftmenu astyle" id={{hostgroup.groupid}} href="#">{{hostgroup.name}}</a> {%endfor%} </div> <div class="right"> </div> </body> </html>
statics里放登录用的证书和jquery文件,还有自定义的js css文件
index.js 里存放页面操作函数
token = $.cookie('csrftoken') $(function(){ hostshow() itemshow() additems() command() delitem() }) function hostshow(){ $('.leftmenu').click(function(){ var hostid = $(this).attr('id') $.ajax({ url:'/zabbix_host.html/', data:{'id':hostid}, type:'POST', headers:{'X-CSRFToken':token}, success:function(host_li){ $('.right').empty() $('.right').append(host_li) } }) }) } function itemshow(){ $('.right').on('click','.host',function(){ var hostid = $(this).attr('hostid') var hostname = $(this).text() var interfaceid = $(this).attr('interfaceid') $.ajax({ url:'/zabbix_items.html/', data:{'hostid':hostid,'interfaceid':interfaceid,'hostname':hostname}, type:'POST', headers:{'X-CSRFToken':token}, success:function(items){ $('.right').empty() $('.right').append(items) } }) }) } function additems(){ $('.right').on('click','.additem',function(){ var hostid = $(this).attr('hostid') var interfaceid = $(this).attr('interfaceid') addlable='<textarea placeholder="itemname 格式示例:郑州电信-10.0.0.1或郑州电信-10.0.0.1,80" class="itemname" rows="20" ></textarea><br>'+ '<button class="command"'+'hostid='+ '\"'+hostid+'\"'+'interfaceid='+'\"'+interfaceid+'\"'+'>提交</button>' $.ajax({ url:'/keylist.html/', data:{'hostid':hostid}, type:'POST', headers:{'X-CSRFToken':token}, success:function(ret){ $('.right').empty() var lab = ret + addlable $('.right').append(lab) } }) }) } function command(){ $('.right').on('click','.command',function(){ var data = $('.itemname').val() var key_ = $('select').val() var hostid = $(this).attr('hostid') var interfaceid = $(this).attr('interfaceid') console.log(data,key_) $.ajax({ url:'/additems.html/', data:{'hostid':hostid,'interfaceid':interfaceid,'data':data,'key_':JSON.stringify(key_)}, type:'POST', headers:{'X-CSRFToken':token}, success:function(ret){ $('.right').empty() $('.right').append(ret) } }) } ) } function delitem(){ $('.right').on('click','.del',function(){ itemid = $(this).attr('itemid') $.ajax({ url:'/delitem.html/', data:{'itemid':itemid}, type:'POST', headers:{'X-CSRFToken':token}, success:function(ret){ $("."+itemid).remove() var htm = $('.right').html() $('.right').empty() $('.right').append(htm) } }) } ) }
jquery.cook,js 官网下载文件,用于django的CSRF,防止跨站
/*! * jQuery Cookie Plugin v1.4.1 * https://github.com/carhartl/jquery-cookie * * Copyright 2006, 2014 Klaus Hartl * Released under the MIT license */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD (Register as an anonymous module) define(['jquery'], factory); } else if (typeof exports === 'object') { // Node/CommonJS module.exports = factory(require('jquery')); } else { // Browser globals factory(jQuery); } }(function ($) { var pluses = /\+/g; function encode(s) { return config.raw ? s : encodeURIComponent(s); } function decode(s) { return config.raw ? s : decodeURIComponent(s); } function stringifyCookieValue(value) { return encode(config.json ? JSON.stringify(value) : String(value)); } function parseCookieValue(s) { if (s.indexOf('"') === 0) { // This is a quoted cookie as according to RFC2068, unescape... s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); } try { // Replace server-side written pluses with spaces. // If we can't decode the cookie, ignore it, it's unusable. // If we can't parse the cookie, ignore it, it's unusable. s = decodeURIComponent(s.replace(pluses, ' ')); return config.json ? JSON.parse(s) : s; } catch(e) {} } function read(s, converter) { var value = config.raw ? s : parseCookieValue(s); return $.isFunction(converter) ? converter(value) : value; } var config = $.cookie = function (key, value, options) { // Write if (arguments.length > 1 && !$.isFunction(value)) { options = $.extend({}, config.defaults, options); if (typeof options.expires === 'number') { var days = options.expires, t = options.expires = new Date(); t.setMilliseconds(t.getMilliseconds() + days * 864e+5); } return (document.cookie = [ encode(key), '=', stringifyCookieValue(value), options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE options.path ? '; path=' + options.path : '', options.domain ? '; domain=' + options.domain : '', options.secure ? '; secure' : '' ].join('')); } // Read var result = key ? undefined : {}, // To prevent the for loop in the first place assign an empty array // in case there are no cookies at all. Also prevents odd result when // calling $.cookie(). cookies = document.cookie ? document.cookie.split('; ') : [], i = 0, l = cookies.length; for (; i < l; i++) { var parts = cookies[i].split('='), name = decode(parts.shift()), cookie = parts.join('='); if (key === name) { // If second argument (value) is a function it's a converter... result = read(cookie, value); break; } // Prevent storing a cookie that we couldn't decode. if (!key && (cookie = read(cookie)) !== undefined) { result[name] = cookie; } } return result; }; config.defaults = {}; $.removeCookie = function (key, options) { // Must not alter options, thus extending a fresh object... $.cookie(key, '', $.extend({}, options, { expires: -1 })); return !$.cookie(key); }; }));
index.css 页面简单渲染
*{margin: 0;padding: 0; } .top{ background-color: rgb(83, 83, 236); height: 52px; } .top-left{ width: 300px;height: 53px; float: left; text-align: center;line-height: 48px; color: seashell; } .top-right{height: 53px; float: right; text-align: center;line-height: 48px; color: seashell; margin-right: 60px; } .left{background-color: whitesmoke; position: absolute;left: 0;top: 48px;bottom: 0; z-index: 99; width: 300px; border-right:1px solid black; z-index: 99; overflow: auto; } .right{ background-color: whitesmoke; position: absolute;left: 301px;top: 48px;bottom: 0;right: 0; z-index: 99; overflow: scroll; z-index: 99; overflow: auto; } a:hover{ background-color: cornflowerblue; } .astyle{ display: block;padding: 10px; } .block{ position: absolute;top: 0;bottom: 0;right: 0;left: 0; background-color: black ;opacity: 0.2;z-index: 100; } .currsor:hover{ cursor: pointer; } .hidden{ display: none; } .pitch{ background-color:black;color: white; } .unpitch{ background-color: white;color: black; } .page{ margin-left: 10px; } textarea{ width: 453px; margin-top: 20px; margin-left: 10px; } .select{ margin-top: 20px; margin-left: 10px; display: block; } .command{ margin-top: 20px; margin-left: 10px; display: block; } table.gridtable { font-family: verdana,arial,sans-serif; font-size:11px; width: 100%; color:#333333; border-width: 1px; border-color: #666666; border-collapse: collapse; } table.gridtable th { border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #dedede; } .change{ width: 70px; } table.gridtable td { border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #ffffff; }
models.py 定义数据库,在这里除了登录用不上
from django.db import models # Create your models here. class login(models.Model): username = models.CharField(max_length=64) passwd = models.CharField(max_length=64) def __str__(self): return self.username
zbauth.py 定义zabbix-api的登录和一系列操作
import requests class ZabbixOperates: def __init__(self): self.zabbix_url = 'https://ex.zabbix.com/api_jsonrpc.php' self.user="user" self.pwd="passwd" self.verify_file,self.ssl_pem,self.ssl_key='/home/user.pem','/home/uer.pem','/home/user.key' self.head={"Content-Type":"application/json"} self.token = self.get_token() def get_token(self): data = { "jsonrpc":"2.0", "method":"user.login", "params":{ "user":self.user, "password":self.pwd }, "id":1 } res = requests.post(self.zabbix_url,headers=self.head,json=data,verify=self.verify_file,cert=(self.ssl_pem,self.ssl_key)) return res.json()['result'] def reuqest(self,data): res = requests.post(self.zabbix_url,json=data,verify=self.verify_file,cert=(self.ssl_pem,self.ssl_key)) return(res.json()) def show(self,value,filter=None): if 'error' in value: return(value['error']) def operate(self,data): return self.reuqest(data) def get_host(self,groupid=None): data={ "jsonrpc": "2.0", "method":"host.get", "params": { "groupids": groupid, "output": [ "hostid", "host", "interfaceid", ], "selectInterfaces": [ "interfaceid" ], "filter":{"type":"1"} }, "id":1, "auth":self.token } return self.reuqest(data) def get_group(self): data={ "jsonrpc": "2.0", "method": "hostgroup.get", "params": { "output": ["name"], }, "auth": self.token, "id": 1 } return self.reuqest(data) def get_item(self,hostid,key): data = { "jsonrpc": "2.0", "method": "item.get", "params": { "output": [ 'itemid', 'name', 'key_' ], "hostids": hostid, "search": { "key_": key }, "sortfield": "name" }, "auth": self.token, "id": 1 } return self.reuqest(data) def create_item(self,*value): hostid,interfaceid,name,key,units=value data={ "jsonrpc": "2.0", "method": "item.create", "params": { "name": name, "key_": key, "hostid": hostid, "type": 0, "value_type": 0, "interfaceid": interfaceid, "delay": '20s', 'history': '90d', 'trends': '365d', 'units': units, 'lifetime': '30d', }, "auth": self.token, "id": 3 } return self.reuqest(data) def del_items(self,itemid): data = { "jsonrpc": "2.0", "method": "item.delete", "params": [ itemid ], "auth": self.token, "id": 1 } return self.reuqest(data)
views.py 存放后端数据处理函数
from django.shortcuts import render,redirect,HttpResponse from django.views.decorators.csrf import csrf_exempt from DarkZabbix import models from functools import wraps from DarkZabbix.zbauth import ZabbixOperates import json # Create your views here. op = ZabbixOperates() def auth(func): @wraps(func) def check_login(res,*args,**kwargs): try: res.session['name'] return func(res,*args,**kwargs) except: return render(res,'login.html') return check_login def deny_get(func): @wraps(func) def deny(res,*args,**kwatgs): if res.method == 'GET': return HttpResponse('已记录此次访问Ip地址') else: return func(res,*args,**kwatgs) return deny def login(res): if res.method == 'GET': return render(res,'login.html') elif res.method == 'POST': username = res.POST.get('username') passwd = res.POST.get('passwd') if models.login.objects.filter(username=username,passwd=passwd): res.session.set_expiry(3600) res.session['name']=username return redirect('/zabbix.html') else: error = '用户名或密码错误' return render(res,'login.html',{'error':error}) @auth def zabbix(res): groupli = op.get_group()['result'] return render(res,'zabbix.html',{'groupli':groupli}) @auth @deny_get def zabbix_host(res): group_id = res.POST.get('id') hostli = op.get_host(groupid=group_id)['result'] lab='' for host in hostli: lab+='<a href="#" class="host astyle" hostid=%s interfaceid=%s>%s</a>'%(host['hostid'],host['interfaces'][0]['interfaceid'],host['host']) return HttpResponse(lab) @auth @deny_get def zabbix_items(res): host_id,interface_id,hostname = res.POST.get('hostid'),res.POST.get('interfaceid'),res.POST.get('hostname') lab='<p>%s <button class="additem" hostid=%s interfaceid=%s>添加item</button></p><table class="gridtable"><tr><td>name</td><td>key</td><td>operate</td></tr>'%(hostname,host_id,interface_id) items = op.get_item(host_id,'ping')['result'] for item in items: lab+='<tr class=%s><td>%s</td><td>%s</td><td><button itemid=%s class=del>删除</button></td></tr>'%(item['itemid'],item['name'],item['key_'],item['itemid']) lab+='</table>' return HttpResponse(lab) @auth @deny_get def key_list(res): import re key_list=['ping_pkloss[*]','ping_restime[*]'] host_id = res.POST.get('hostid') items = op.get_item(host_id,'ping')['result'] for item in items: key_ = item['key_'] try: host = re.findall('\[.*\]',key_)[0] key_= key_.replace(host,'[*]') except: pass if key_ not in key_list: key_list.append(key_) labli=['<option>%s</option>'%x for x in key_list] lab = '<p class="select">key: <select multiple="multiple">'+''.join(labli)+'</select></p>' return HttpResponse(lab) @auth @deny_get def add_items(res): hostid,interfaceid,itemname,key_list = res.POST.get('hostid'),res.POST.get('interfaceid'),res.POST.get('data'),res.POST.get('key_') key_list = json.loads(key_list) if not key_list: return HttpResponse('缺少必选项或格式错误') items = itemname.split('\n') for item in items: name = item item = item.strip() _,host = item.split('-') for key_ in key_list: key_ = key_.replace('*',host) type_ = 'restime' if 'restime' in key_ else 'pkloss' unit = 'ms' if type_ =='restime' else '%' ret = op.create_item(hostid,interfaceid,name+type_,key_,unit) if op.show(ret): return HttpResponse(json.dumps(op.show(ret))) lab='操作成功' return HttpResponse(lab) @auth @deny_get def del_items(res): itemid = res.POST.get('itemid') ret = op.del_items(itemid) if op.show(ret): return HttpResponse(json.show(op.show(ret))) lab='操作成功' return HttpResponse(lab)
ending......
以驱魔为理想,为生计而奔波