rework of super-server using jinja2

This commit is contained in:
Kevin Francoise 2023-01-19 19:28:14 +01:00
parent 2ba2c988ea
commit 2bef6efea6
6 changed files with 447 additions and 643 deletions

View File

@ -1,10 +1,17 @@
aiohttp==3.5.4 aiohttp==3.6.3
aiohttp-jinja2==1.5
aiosignal==1.3.1
async-timeout==3.0.1 async-timeout==3.0.1
attrs==20.3.0 attrs==20.3.0
cachetools==5.2.1
chardet==3.0.4 chardet==3.0.4
charset-normalizer==2.1.1
frozenlist==1.3.3
idna==2.10 idna==2.10
idna-ssl==1.1.0 idna-ssl==1.1.0
Jinja2==3.1.2
MarkupSafe==2.1.1
multidict==4.7.6 multidict==4.7.6
typing-extensions==3.7.4.3 typing-extensions==3.7.4.3
yarl==1.6.3 yarl==1.5.1
zabbix-api==0.5.5 zabbix-api==0.5.5

13
settings.py Normal file
View File

@ -0,0 +1,13 @@
ZABBIX_API = 'http://127.0.0.1:8080'
ZABBIX_URL = 'https://zabbix.internal'
ZABBIX_LOGIN = 'Admin'
ZABBIX_PASS = 'admin'
LIMIT = 3000
HOSTGROUP = "Zabbix*"
SEVERITY = 3
TIMEOUT = 20
PORT = 8080
ZABBIX_SERVERS_CHECK = ['127.0.0.1:10051']
COLOR_TEAM = {
"Zabbix servers": "#04B404"
}

View File

@ -1,328 +0,0 @@
import aiohttp_jinja2
import jinja2
import aiohttp
import settings
import time
import datetime
import logging
import sys
import random
import os
import json
import asyncio
import socket
from aiohttp import web
from zabbix_api import ZabbixAPI
stdio_handler = logging.StreamHandler()
stdio_handler.setLevel(logging.INFO)
_logger = logging.getLogger('aiohttp.access')
_logger.addHandler(stdio_handler)
_logger.setLevel(logging.INFO)
def convert_seconds(seconds):
time = "a few sec"
if seconds >= 60:
time = str(round(seconds / 60)) + " min"
if seconds > 3600:
if round(seconds / 3600) == 1:
time = "an hour"
else:
time = str(round(seconds / 3600)) + " hours"
if seconds >= 86400:
if round(seconds / 86400) == 1:
time = "a day"
else:
time = str(round(seconds / 86400)) + " days"
if seconds >= 2629746:
if round(seconds / 2629746) == 1:
time = "a month"
else:
time = str(round(seconds / 2629746)) + " months"
if seconds >= 31536000:
if round(seconds / 31536000) == 1:
time = "a year"
else:
time = str(round(seconds / 31536000)) + " years"
return time
def zabbix_login():
global zapi
x = 0
while x < 10:
try:
zapi = ZabbixAPI(server=settings.ZABBIX_API, timeout=int(settings.TIMEOUT))
zapi.login(settings.ZABBIX_LOGIN, settings.ZABBIX_PASS)
except Exception as e:
x+=1
_logger.error("[ERR] - {} Retry #{}: {}".format(datetime.datetime.now(), x, e))
time.sleep(x)
continue
break
if x >= 10:
sys.exit("Can't connect to Zabbix API.")
def zabbix_call(request, method):
global zapi
x = 0
while x < 10:
try:
if method == 'hostgroup':
resp = zapi.hostgroup.get(request)
elif method == 'triggers':
resp = zapi.trigger.get(request)
except Exception as e:
x+=1
_logger.error("[ERR] - {} Retry #{}: {}".format(datetime.datetime.now(), x, e))
time.sleep(x)
continue
break
if x >= 10:
sys.exit("Can't perform calls to Zabbix API.")
else:
return resp
def read_file(file_to_update):
if os.path.exists(file_to_update):
with open(file_to_update, 'r') as f:
out = json.load(f)
f.close()
return out
return False
def write_file(notes, file_to_update):
with open(file_to_update, 'w+') as f:
json.dump(notes, f)
f.close()
def display_notes(request):
note_list = []
notes = read_file('./data/motd.json')
teams = request.match_info.get('teams')
if notes:
for ts in notes:
for note in notes[ts]:
if note['team'] in teams or note['team'] == 'all':
date_note = datetime.datetime.utcfromtimestamp(int(ts)).strftime('%Y-%m-%d %H:%M:%S')
note_list.append({"lvl": note["lvl"], "date": date_note, "msg": note['msg'], "name": note['name'], "ts": ts })
return note_list
def get_hostgroup_color(hostgroup):
if hostgroup not in settings.COLOR_TEAM:
color = "#%06x;" % random.randint(0, 0xFFFFFF)
settings.COLOR_TEAM[hostgroup] = color
else:
color = settings.COLOR_TEAM[hostgroup]
return color
def get_hostgroups():
groups = []
for hg in settings.HOSTGROUP:
request = dict()
request['output'] = 'extend'
request['search'] = dict()
request['search']['name'] = hg
request['searchWildcardsEnabled'] = 1
resp = zabbix_call(request, 'hostgroup')
for x in range(len(resp)):
groups.append(resp[x]['name'])
return groups
def get_ttl_hash(seconds=30):
return round(time.time() / seconds)
### CACHE
def get_problems(request, ttl_hash = None):
del ttl_hash
problems = []
limit = settings.LIMIT
groups = [group.lower() for group in get_hostgroups()]
try:
zapi.logged_in()
except Exception as e:
_logger.info('[INFO] - {}: Connection to Zabbix API'.format(datetime.datetime.now()))
zabbix_login()
team_list = settings.HOSTGROUP
groupids = []
for hg in team_list:
request = dict()
request['output'] = 'extend'
request['search'] = dict()
request['search']['name'] = hg
request['searchWildcardsEnabled'] = 1
resp = zabbix_call(request, 'hostgroup')
for x in range(len(resp)):
groupids.append(int(resp[x]['groupid']))
request = dict()
request['limit'] = limit
request['groupids'] = groupids
request['monitored'] = 1
request['maintenance'] = 0
request['active'] = 1
request['min_severity'] = settings.SEVERITY
request['output'] = "extend"
request['expandData'] = 1
request['selectHosts'] = "extend"
request['selectGroups'] = "extend"
request['expandDescription'] = 1
request['only_true'] = 1
request['skipDependent'] = 1
request['withUnacknowledgedEvents'] = 1
request['withLastEventUnacknowledged'] = 1
request['selectTags'] = "extend"
request['filter'] = dict()
request['filter']['value'] = 1
request['sortfield'] = ["priority","lastchange"]
request['sortorder'] = ["DESC"]
resp = zabbix_call(request, 'triggers')
for x in range(len(resp)):
data = resp[x]
if len(data['hosts']) > 0:
# Loop on problems
for z in range(len(data['groups'])):
if int(data['groups'][z]['groupid']) in groupids:
hostgroup = data['groups'][z]['name']
color = get_hostgroup_color(hostgroup)
since = convert_seconds(int(time.time()) - int(data['lastchange']))
problems.append({'description': data['description'], 'host': data['hosts'][0]['host'], 'priority': data['priority'], 'triggerid': data['triggerid'], 'since': since, 'hostgroup': hostgroup, 'color': color, 'lastchange': data['lastchange']})
for y in range(len(data['tags'])):
tag_value = data['tags'][y]['value']
if tag_value.lower() in groups:
color = get_hostgroup_color(tag_value)
since = convert_seconds(int(time.time()) - int(data['lastchange']))
problems.append({'description': data['description'], 'host': data['hosts'][0]['host'], 'priority': data['priority'], 'triggerid': data['triggerid'], 'since': since, 'hostgroup': tag_value, 'color': color, 'lastchange': data['lastchange']})
_logger.info('[INFO] - {}: Refresh...'.format(datetime.datetime.now()))
return problems
async def post_note(request):
data = await request.post()
msg = data['msg']
team = data['team']
name = data['name']
url = data['url']
lvl = data['lvl']
ts = int(time.time())
j = {}
j[ts] = []
j[ts].append({
'team': team,
'name': name,
'msg': msg,
'lvl': lvl
})
if os.path.exists('./data/motd.json'):
out = read_file('./data/motd.json')
out.update(j)
write_file(out, './data/motd.json')
else:
write_file(j, './data/motd.json')
_logger.info("[ADD] - {}".format(j))
return aiohttp.web.HTTPFound(location=url, text='{}'.format(ts), content_type='text/html')
async def del_note(request):
data = await request.post()
note_id = data['note_id']
url = data['url']
out = read_file('./data/motd.json')
_logger.info('[DEL] - {}'.format(out[note_id]))
del out[note_id]
write_file(out, './data/motd.json')
return aiohttp.web.HTTPFound(location=url)
async def check_servers():
while True:
_logger.info('[INFO] - Checking Zabbix Servers: {}'.format(settings.ZABBIX_SERVERS_CHECK))
j = {}
for ip in settings.ZABBIX_SERVERS_CHECK:
port = 10051
if ':' in ip:
i = ip.split(':')
ip = i[0]
port = int(i[1].strip())
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
result = sock.connect_ex((ip, port))
if result == 0:
_logger.info("[INFO] - Port {} OK: {}".format(port, ip))
else:
_logger.error("[ERR] - Port {} KO: {}".format(port, ip))
j[ip] = result
sock.close()
if os.path.exists('./data/zabbix-servers.json'):
out = read_file('./data/zabbix-servers.json')
out.update(j)
write_file(out, './data/zabbix-servers.json')
else:
write_file(j, './data/zabbix-servers.json')
await asyncio.sleep(60)
async def start_background_tasks(app):
app['dispatch'] = app.loop.create_task(check_servers())
async def cleanup_background_tasks(app):
app['dispatch'].cancel()
await app['dispatch']
@aiohttp_jinja2.template('index.html')
def display_alerts(request):
url = str(request.url)
if '/tv/' in url:
tv_mode = True
else:
tv_mode = False
teams = request.match_info.get('teams')
try:
zapi.logged_in()
except Exception as e:
_logger.info('[INFO] - {}: Connection to Zabbix API'.format(datetime.datetime.now()))
zabbix_login()
if teams:
team_list = teams.lower().split('+')
check_servers = read_file('./data/zabbix-servers.json')
notes = display_notes(request)
problems = get_problems(request, ttl_hash=get_ttl_hash())
_logger.info(len(problems))
problems = [problem for problem in problems if problem.get('hostgroup').lower() in team_list]
problems = sorted(problems,
key = lambda i: (i['priority'], i['lastchange']),
reverse=True)
_logger.info('[NB ALERTS] - {}'.format(len(problems)))
context = {'alerts': problems, 'total_alerts': len(problems), 'zabbix_url': settings.ZABBIX_URL, "hostgroups": get_hostgroups(), "notes": notes, "tv_mode": tv_mode, "check_servers": check_servers}
return aiohttp_jinja2.render_template(
'index.html', request, context)
def main():
app = web.Application()
aiohttp_jinja2.setup(app,
loader=jinja2.FileSystemLoader('templates'))
app.add_routes([
web.get('/', display_alerts),
web.get('/{teams}', display_alerts),
web.get('/tv/{teams}', display_alerts),
aiohttp.web.post('/post', post_note),
aiohttp.web.post('/del', del_note),
aiohttp.web.static('/images', 'images'),
aiohttp.web.static('/css', 'css'),
aiohttp.web.static('/js', 'js')
])
app.on_startup.append(start_background_tasks)
app.on_cleanup.append(cleanup_background_tasks)
aiohttp.web.run_app(app, port=settings.PORT)
if __name__ == '__main__':
main()

View File

@ -1,65 +1,25 @@
from zabbix_api import ZabbixAPI import aiohttp_jinja2
from os import path import jinja2
import asyncio
import json
import operator
import aiohttp import aiohttp
import aiohttp.web import settings
import json
import time import time
import datetime import datetime
import logging import logging
import sys import sys
import os.path
import random import random
import argparse import os
import re import json
import config import asyncio
import socket import socket
from aiohttp import web
from zabbix_api import ZabbixAPI
from functools import lru_cache
stdio_handler = logging.StreamHandler() stdio_handler = logging.StreamHandler()
stdio_handler.setLevel(logging.INFO) stdio_handler.setLevel(logging.INFO)
_logger = logging.getLogger('aiohttp.access') _logger = logging.getLogger('aiohttp.access')
_logger.addHandler(stdio_handler) _logger.addHandler(stdio_handler)
_logger.setLevel(logging.DEBUG) _logger.setLevel(logging.INFO)
parser = argparse.ArgumentParser()
parser.add_argument("--zabbix_ip", help="Zabbix Frontend IP.", required=True)
parser.add_argument("--zabbix_url", help="Zabbix URL. Used to build triggers URL.", required=True)
parser.add_argument("--alert_limit", help="Number of alerts to retrieve.", required=True)
parser.add_argument("--zabbix_hostgroup", help="Search for hostgroups which correspond to this parameter (Wildcard allowed).", required=True)
parser.add_argument("--zabbix_min_severity", help="Minimum trigger severity to retrieve.", required=True)
parser.add_argument("--zabbix_login", help="Login to connect to the Zabbix API.", required=True)
parser.add_argument("--zabbix_pass", help="Password to connect to the Zabbix API.", required=True)
parser.add_argument("--list_zabbix_servers", help="List of Zabbix Servers to check", required=True, nargs="+")
parser.add_argument("--zabbix_timeout", help="Timeout to the API.", required=True, type=int)
parser.add_argument("--port", help="Listen Port.", required=True)
args = parser.parse_args()
ZABBIX_API = 'http://'+args.zabbix_ip
ZABBIX_FRONTEND = args.zabbix_url + '/'
ZABBIX_LOGIN = args.zabbix_login
ZABBIX_PASS = args.zabbix_pass
LIMIT = args.alert_limit
HOSTGROUP = args.zabbix_hostgroup
SEVERITY = args.zabbix_min_severity
TIMEOUT = args.zabbix_timeout
PORT = args.port
ZABBIX_SERVERS_CHECK = args.list_zabbix_servers
_logger.info('\n[ENVIRONMENT VARIABLES]\n[ZABBIX API] - {}'.format(ZABBIX_API))
_logger.info('[ZABBIX FRONTEND] - {}'.format(ZABBIX_FRONTEND))
_logger.info('[ALERT LIMIT] - {}'.format(LIMIT))
_logger.info('[HOSTGROUP] - {}'.format(HOSTGROUP))
_logger.info('[MIN SEVERITY] - {}'.format(SEVERITY))
_logger.info('[TIMEOUT] - {}'.format(TIMEOUT))
_logger.info('[PORT] - {}'.format(PORT))
_logger.info('[ZABBIX SERVERS TO CHECK] - {}\n'.format(ZABBIX_SERVERS_CHECK))
zapi = None
def convert_seconds(seconds): def convert_seconds(seconds):
time = "a few sec" time = "a few sec"
@ -87,92 +47,160 @@ def convert_seconds(seconds):
time = str(round(seconds / 31536000)) + " years" time = str(round(seconds / 31536000)) + " years"
return time return time
def severity_badge(severity): def zabbix_login():
if severity == 5: global zapi
badge = '<span class="octicon octicon-flame"> </span>' x = 0
elif severity == 4:
badge = '<span class="badge badge-pill badge-danger">4</span>' while x < 10:
elif severity == 3: try:
badge = '<span class="badge badge-pill badge-warning">3</span>' zapi = ZabbixAPI(server=settings.ZABBIX_API, timeout=int(settings.TIMEOUT))
elif severity == 2: zapi.login(settings.ZABBIX_LOGIN, settings.ZABBIX_PASS)
badge = '<span class="badge badge-pill badge-primary">2</span>' except Exception as e:
elif severity == 1: x+=1
badge = '<span class="badge badge-pill badge-info">1</span>' _logger.error("[ERR] - {} Retry #{}: {}".format(datetime.datetime.now(), x, e))
time.sleep(x)
continue
break
if x >= 10:
sys.exit("Can't connect to Zabbix API.")
def zabbix_call(request, method):
global zapi
x = 0
while x < 10:
try:
if method == 'hostgroup':
resp = zapi.hostgroup.get(request)
elif method == 'triggers':
resp = zapi.trigger.get(request)
except Exception as e:
x+=1
_logger.error("[ERR] - {} Retry #{}: {}".format(datetime.datetime.now(), x, e))
time.sleep(x)
continue
break
if x >= 10:
sys.exit("Can't perform calls to Zabbix API.")
else: else:
badge = '<span class="badge badge-pill badge-light">0</span>' return resp
return badge
def severity_css(severity): def read_file(file_to_update):
css = "" if os.path.exists(file_to_update):
if severity == 5: with open(file_to_update, 'r') as f:
css = 'class="disaster"' out = json.load(f)
return css f.close()
return out
return False
def html_response(data): def write_file(notes, file_to_update):
response = "" with open(file_to_update, 'w+') as f:
for x in range(len(data)): json.dump(notes, f)
seconds = int(time.time()) - int(data[x]['lastchange']) f.close()
since = convert_seconds(seconds)
css_class = severity_css(int(data[x]['priority']))
badge = severity_badge(int(data[x]['priority']))
if data[x]['team'] not in config.COLOR_TEAM:
color = "#%06x;" % random.randint(0, 0xFFFFFF)
config.COLOR_TEAM[data[x]['team']] = color
else:
color = config.COLOR_TEAM[data[x]['team']]
response += "<tr "+css_class+"><td><span class='badge badge-pill' style='background-color:"+color+"'>"+data[x]['team']+"</span></td><td>"+data[x]['host']+"</td><td><a href='"+ZABBIX_FRONTEND+"zabbix.php?action=problem.view&filter_set=1&filter_triggerids%5B%5D="+data[x]['triggerid']+"' target='_blank' "+css_class+">"+data[x]['description']+"</a></td><td>"+badge+"</td><td><span class='badge badge-pill badge-light'>"+since+"</span></td></tr>"
return response def display_notes(request):
note_list = []
notes = read_file('./data/motd.json')
teams = request.match_info.get('teams')
if notes:
for ts in notes:
for note in notes[ts]:
if note['team'] in teams or note['team'] == 'all':
date_note = datetime.datetime.utcfromtimestamp(int(ts)).strftime('%Y-%m-%d %H:%M:%S')
note_list.append({"lvl": note["lvl"], "date": date_note, "msg": note['msg'], "name": note['name'], "ts": ts })
return note_list
def capitalize_nth(s, n): def get_hostgroup_color(hostgroup):
if len(s) == 2: if hostgroup not in settings.COLOR_TEAM:
return s[:n].upper() + s[n:].capitalize() color = "#%06x;" % random.randint(0, 0xFFFFFF)
settings.COLOR_TEAM[hostgroup] = color
else: else:
return s.title() color = settings.COLOR_TEAM[hostgroup]
return color
def get_hostgroups():
groups = []
for hg in settings.HOSTGROUP:
request = dict()
request['output'] = 'extend'
request['search'] = dict()
request['search']['name'] = hg
request['searchWildcardsEnabled'] = 1
resp = zabbix_call(request, 'hostgroup')
for x in range(len(resp)):
groups.append(resp[x]['name'])
return groups
def construct_menu(): def get_ttl_hash(seconds=45):
MENU = "" return round(time.time() / seconds)
for key in config.CONTENT.keys(): @lru_cache()
MENU += "<li class='nav-item'><a class='nav-link' href='/"+key+"'>"+ capitalize_nth(key.replace('Team-', ''),2) +"</a></li>" def get_problems(ttl_hash = None):
return MENU del ttl_hash
problems = []
def construct_form(): limit = settings.LIMIT
FORM = "<div class='col'><select class='form-control' name='team'><option value='all'>All teams</option>" groups = [group.lower() for group in get_hostgroups()]
try:
for key in config.CONTENT.keys(): zapi.logged_in()
FORM += "<option value='"+key+"'>"+key+"</option>" except Exception as e:
FORM += "</select></div>" _logger.info('[INFO] - {}: Connection to Zabbix API'.format(datetime.datetime.now()))
return FORM zabbix_login()
async def check_servers(): team_list = settings.HOSTGROUP
global ZABBIX_SERVERS_CHECK
groupids = []
while True: for hg in team_list:
_logger.info('[INFO] - Checking Zabbix Servers: {}'.format(ZABBIX_SERVERS_CHECK)) req = dict()
j = {} req['output'] = 'extend'
for ip in ZABBIX_SERVERS_CHECK: req['search'] = dict()
port = 10051 req['search']['name'] = hg
if ':' in ip: req['searchWildcardsEnabled'] = 1
i = ip.split(':') resp = zabbix_call(req, 'hostgroup')
ip = i[0] for x in range(len(resp)):
port = int(i[1].strip()) groupids.append(int(resp[x]['groupid']))
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5) req = dict()
result = sock.connect_ex((ip, port)) req['limit'] = limit
if result == 0: req['groupids'] = groupids
_logger.info("[INFO] - Port {} OK: {}".format(port, ip)) req['monitored'] = 1
else: req['maintenance'] = 0
_logger.error("[ERR] - Port {} KO: {}".format(port, ip)) req['active'] = 1
j[ip] = result req['min_severity'] = settings.SEVERITY
sock.close() req['output'] = "extend"
if os.path.exists('./data/zabbix-servers.json'): req['expandData'] = 1
out = read_file('./data/zabbix-servers.json') req['selectHosts'] = "extend"
out.update(j) req['selectGroups'] = "extend"
write_file(out, './data/zabbix-servers.json') req['expandDescription'] = 1
else: req['only_true'] = 1
write_file(j, './data/zabbix-servers.json') req['skipDependent'] = 1
await asyncio.sleep(60) req['withUnacknowledgedEvents'] = 1
req['withLastEventUnacknowledged'] = 1
req['selectTags'] = "extend"
req['filter'] = dict()
req['filter']['value'] = 1
req['sortfield'] = ["priority","lastchange"]
req['sortorder'] = ["DESC"]
resp = zabbix_call(req, 'triggers')
for x in range(len(resp)):
data = resp[x]
if len(data['hosts']) > 0:
# Loop on problems
for z in range(len(data['groups'])):
if int(data['groups'][z]['groupid']) in groupids:
hostgroup = data['groups'][z]['name']
color = get_hostgroup_color(hostgroup)
since = convert_seconds(int(time.time()) - int(data['lastchange']))
problems.append({'description': data['description'], 'host': data['hosts'][0]['host'], 'priority': data['priority'], 'triggerid': data['triggerid'], 'since': since, 'hostgroup': hostgroup, 'color': color, 'lastchange': data['lastchange']})
for y in range(len(data['tags'])):
tag_value = data['tags'][y]['value']
if tag_value.lower() in groups:
color = get_hostgroup_color(tag_value)
since = convert_seconds(int(time.time()) - int(data['lastchange']))
problems.append({'description': data['description'], 'host': data['hosts'][0]['host'], 'priority': data['priority'], 'triggerid': data['triggerid'], 'since': since, 'hostgroup': tag_value, 'color': color, 'lastchange': data['lastchange']})
_logger.info('[INFO] - {}: Refresh...'.format(datetime.datetime.now()))
return problems
async def post_note(request): async def post_note(request):
data = await request.post() data = await request.post()
@ -201,19 +229,6 @@ async def post_note(request):
_logger.info("[ADD] - {}".format(j)) _logger.info("[ADD] - {}".format(j))
return aiohttp.web.HTTPFound(location=url, text='{}'.format(ts), content_type='text/html') return aiohttp.web.HTTPFound(location=url, text='{}'.format(ts), content_type='text/html')
def read_file(file_to_update):
if os.path.exists(file_to_update):
with open(file_to_update, 'r') as f:
out = json.load(f)
return out
f.close()
return False
def write_file(notes, file_to_update):
with open(file_to_update, 'w+') as f:
json.dump(notes, f)
f.close()
async def del_note(request): async def del_note(request):
data = await request.post() data = await request.post()
note_id = data['note_id'] note_id = data['note_id']
@ -224,195 +239,90 @@ async def del_note(request):
write_file(out, './data/motd.json') write_file(out, './data/motd.json')
return aiohttp.web.HTTPFound(location=url) return aiohttp.web.HTTPFound(location=url)
async def show_alerts(request): async def check_servers():
global CONTENT
global NAVBAR
global JS_CONTENT
global TEMPLATE_FOOTER
global TOTAL_ALERTS
data_list = []
html_content = ""
html_notes = "<table class='table table-borderless'>"
html_check = "<table class='table table-borderless table-sm'>"
response = ""
config.TEMPLATE_HEAD = config.TEMPLATE_HEAD.replace('FORM_TEAM', construct_form())
config.NAVBAR = config.NAVBAR.replace('LIST', construct_menu())
config.NAVBAR = re.sub('\[[0-9]+\]', '['+config.TOTAL_ALERTS+']', config.NAVBAR)
url = str(request.url)
if '/tv/' in url:
config.TEMPLATE_FOOTER = config.TEMPLATE_FOOTER.replace('show', 'hide')
else:
config.TEMPLATE_FOOTER = config.TEMPLATE_FOOTER.replace('hide', 'show')
teams = request.match_info.get('teams')
if not teams:
teams = []
notes = read_file('./data/motd.json')
if notes:
for ts in notes:
for note in notes[ts]:
if note['team'] in teams or note['team'] == 'all':
date_note = datetime.datetime.utcfromtimestamp(int(ts)).strftime('%Y-%m-%d %H:%M:%S')
html_notes += "<tr class='bg-"+note['lvl']+"'><td class='text-left'><span class='octicon octicon-clock'></span> "+date_note+"</td><td class='text-center'> "+note['msg']+"</td><td class='text-right'><span class='octicon octicon-hubot'></span><i> (by "+note['name']+")</i></td><td class='text-right'><form action='/del' method='post' accept-charset='utf-8' enctype='application/x-www-form-urlencoded'><input type='text' class='form-control url_note' name='url' hidden readonly><input type='text' name='note_id' value='"+ts+"' readonly hidden><button type='submit' class='btn btn-outline-light btn-sm' id='del_note' ><span class='octicon octicon-trashcan'></span></button></form></td></tr>"
html_notes += '</table>'
check_zbx = read_file('./data/zabbix-servers.json')
if check_zbx:
for ip in check_zbx:
if check_zbx[ip] != 0:
html_check += "<tr class='bg-danger'><td class='text-center align-left'><span class='octicon octicon-alert'></span> Zabbix Server: "+ ip + " seems UNREACHABLE! <span class='octicon octicon-alert'></span></td></tr>"
html_check += '</table>'
IMAGE='<div class="container-fluid image-container"><img src="/images/zabbix_logo_500x131.png" width="30%"/></div>'
if teams:
team_list = teams.split('+')
for team in team_list:
team_alerts = config.CONTENT.get(team, {})
data_list.extend(team_alerts)
if data_list:
data = sorted(data_list,
key = lambda i: (i['priority'], i['lastchange']),
reverse=True)
result = html_response(data)
html_content = config.TEMPLATE_HEAD.replace('[alerts]', '['+str(len(data))+']').replace('NAVBAR', config.NAVBAR).replace('NOTES', html_notes).replace('CHECK', html_check) + result + config.TEMPLATE_FOOTER
else:
html_content = config.TEMPLATE_HEAD.replace('NAVBAR', config.NAVBAR).replace('NOTES', html_notes).replace('CHECK', html_check) + '<div class="alert alert-dark text-center" role="alert">No Alerts for '+str(teams)+'! <span class="octicon octicon-thumbsup"></span></div>'+IMAGE+' ' + config.TEMPLATE_FOOTER
else:
html_content = config.TEMPLATE_HEAD.replace('NAVBAR', config.NAVBAR) + response + config.TEMPLATE_FOOTER
return aiohttp.web.Response(text=html_content, content_type='text/html')
def zabbix_login():
global zapi
x = 0
while x < 10:
try:
zapi = ZabbixAPI(server=ZABBIX_API, timeout=int(TIMEOUT))
zapi.login(ZABBIX_LOGIN, ZABBIX_PASS)
except Exception as e:
x+=1
_logger.error("[ERR] - {} Retry #{}: {}".format(datetime.datetime.now(), x, e))
time.sleep(x)
continue
break
if x >= 10:
sys.exit("Can't connect to Zabbix API.")
def call_zabbix(request, method):
global zapi
x = 0
while x < 10:
try:
if method == 'hostgroup':
resp = zapi.hostgroup.get(request)
elif method == 'triggers':
resp = zapi.trigger.get(request)
except Exception as e:
x+=1
_logger.error("[ERR] - {} Retry #{}: {}".format(datetime.datetime.now(), x, e))
time.sleep(x)
continue
break
if x >= 10:
sys.exit("Can't perform calls to Zabbix API.")
else:
return resp
async def process_zabbix_queue():
global CONTENT
global TOTAL_ALERTS
global HOSTGROUP
global LIMIT
global SEVERITY
global zapi
while True: while True:
try: _logger.info('[INFO] - Checking Zabbix Servers: {}'.format(settings.ZABBIX_SERVERS_CHECK))
zapi.logged_in() j = {}
except Exception as e: for ip in settings.ZABBIX_SERVERS_CHECK:
_logger.info('[INFO] - {}: Connection to Zabbix API'.format(datetime.datetime.now())) port = 10051
zabbix_login() if ':' in ip:
i = ip.split(':')
request = dict() ip = i[0]
request['output'] = 'extend' port = int(i[1].strip())
request['search'] = dict() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
request['search']['name'] = [HOSTGROUP] sock.settimeout(5)
request['searchWildcardsEnabled'] = 1 result = sock.connect_ex((ip, port))
resp = call_zabbix(request, 'hostgroup') if result == 0:
groupids = [] _logger.info("[INFO] - Port {} OK: {}".format(port, ip))
for x in range(len(resp)): else:
config.CONTENT[resp[x]['name']] = [] _logger.error("[ERR] - Port {} KO: {}".format(port, ip))
groupids.append(resp[x]['groupid']) j[ip] = result
sock.close()
request = dict() if os.path.exists('./data/zabbix-servers.json'):
request['limit'] = LIMIT out = read_file('./data/zabbix-servers.json')
request['groupids'] = groupids out.update(j)
request['monitored'] = 1 write_file(out, './data/zabbix-servers.json')
request['maintenance'] = 0 else:
request['active'] = 1 write_file(j, './data/zabbix-servers.json')
request['min_severity'] = SEVERITY await asyncio.sleep(60)
request['output'] = "extend"
request['expandData'] = 1
request['selectHosts'] = "extend"
request['selectGroups'] = "extend"
request['expandDescription'] = 1
request['only_true'] = 1
request['skipDependent'] = 1
request['withUnacknowledgedEvents'] = 1
request['withLastEventUnacknowledged'] = 1
request['selectTags'] = "extend"
request['filter'] = dict()
request['filter']['value'] = 1
request['sortfield'] = ["priority","lastchange"]
request['sortorder'] = ["DESC"]
_logger.info(request)
resp = call_zabbix(request, 'triggers')
config.TOTAL_ALERTS = str(len(resp))
_logger.info('[NB ALERTS] - {}'.format(config.TOTAL_ALERTS))
hostgroup_to_search = HOSTGROUP.replace('*','')
for x in range(len(resp)):
data = resp[x]
if len(data['hosts']) > 0:
for z in range(len(data['groups'])):
if hostgroup_to_search in data['groups'][z]['name']:
group = data['groups'][z]['name']
config.CONTENT[group].append({'description': data['description'], 'host': data['hosts'][0]['host'], 'priority': data['priority'], 'triggerid': data['triggerid'], 'lastchange': data['lastchange'], 'team': group})
for y in range(len(data['tags'])):
if 'team' == data['tags'][y]['tag']:
team = data['tags'][y]['value']
config.CONTENT[team].append({'description': data['description'], 'host': data['hosts'][0]['host'], 'priority': data['priority'], 'triggerid': data['triggerid'], 'lastchange': data['lastchange'], 'team': team})
_logger.info('[INFO] - {}: Refresh...'.format(datetime.datetime.now()))
await asyncio.sleep(15)
async def start_background_tasks(app): async def start_background_tasks(app):
app['dispatch'] = app.loop.create_task(process_zabbix_queue())
app['dispatch'] = app.loop.create_task(check_servers()) app['dispatch'] = app.loop.create_task(check_servers())
async def cleanup_background_tasks(app): async def cleanup_background_tasks(app):
app['dispatch'].cancel() app['dispatch'].cancel()
await app['dispatch'] await app['dispatch']
@aiohttp_jinja2.template('index.html')
def display_alerts(request):
url = str(request.url)
if '/tv/' in url:
tv_mode = True
else:
tv_mode = False
teams = request.match_info.get('teams')
try:
zapi.logged_in()
except Exception as e:
_logger.info('[INFO] - {}: Connection to Zabbix API'.format(datetime.datetime.now()))
zabbix_login()
if teams:
team_list = teams.lower().split('+')
check_servers = read_file('./data/zabbix-servers.json')
notes = display_notes(request)
problems = get_problems(ttl_hash=get_ttl_hash())
_logger.info(len(problems))
problems = [problem for problem in problems if problem.get('hostgroup').lower() in team_list]
problems = sorted(problems,
key = lambda i: (i['priority'], i['lastchange']),
reverse=True)
_logger.info('[NB ALERTS] - {}'.format(len(problems)))
context = {'alerts': problems, 'total_alerts': len(problems), 'zabbix_url': settings.ZABBIX_URL, "hostgroups": get_hostgroups(), "notes": notes, "tv_mode": tv_mode, "check_servers": check_servers}
return aiohttp_jinja2.render_template(
'index.html', request, context)
def main(): def main():
app = web.Application()
aiohttp_jinja2.setup(app,
loader=jinja2.FileSystemLoader('templates'))
app = aiohttp.web.Application(logger=_logger) app.add_routes([
web.get('/', display_alerts),
app.add_routes([aiohttp.web.get('/', show_alerts), web.get('/{teams}', display_alerts),
web.get('/tv/{teams}', display_alerts),
aiohttp.web.post('/post', post_note), aiohttp.web.post('/post', post_note),
aiohttp.web.post('/del', del_note), aiohttp.web.post('/del', del_note),
aiohttp.web.get('/{teams}', show_alerts),
aiohttp.web.get('/tv/{teams}', show_alerts),
aiohttp.web.static('/images', 'images'), aiohttp.web.static('/images', 'images'),
aiohttp.web.static('/css', 'css'), aiohttp.web.static('/css', 'css'),
aiohttp.web.static('/js', 'js')]) aiohttp.web.static('/js', 'js')
])
app.on_startup.append(start_background_tasks) app.on_startup.append(start_background_tasks)
app.on_cleanup.append(cleanup_background_tasks) app.on_cleanup.append(cleanup_background_tasks)
aiohttp.web.run_app(app, port=PORT) aiohttp.web.run_app(app, port=settings.PORT)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

6
templates/form.html Normal file
View File

@ -0,0 +1,6 @@
<div class='col'><select class='form-control' name='team'>
<option value='all'>All teams</option>
{% for hostgroup in hostgroups %}
<option value="{{ hostgroup }}">{{ hostgroup }}</option>
{% endfor %}
</select></div>

196
templates/index.html Normal file
View File

@ -0,0 +1,196 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>[{{total_alerts}}] Super(Vision)</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/octicons.min.css">
<link rel="stylesheet" href="/css/offline.css">
<script src="/js/jquery-3.4.1.slim.min.js"></script>
<script type="text/javascript" src="/js/dynafav-1.0.js"></script>
<script src="/js/popper.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/offline.min.js" ></script>
<style type="text/css">
a {
color: #DF7401;
font-weight: bolder;
}
a:hover {
text-decoration: none;
color: #DF7401;
}
body {
background-color: #333;
font-weight: bolder;
}
.disaster {
background-color:#D30801;
color: #FFFFFF !important;
}
.disaster a {
color: white;
}
.table td, .table th { padding: 1px; !important }
.image-container {
display: flex;
justify-content: center;
align-items: center;
padding-top: 15%;
}
.btn-group-sm>.btn, .btn-sm {
padding: .08rem .5rem;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" href="/">[{{total_alerts}}] Super(Vision)</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse navbar-sm" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
{% for hostgroup in hostgroups %}
<li class='nav-item'><a class='nav-link' href='/{{hostgroup}}'>{{hostgroup}}</a></li>
{%endfor%}
</ul>
<button class="btn btn-outline-secondary" type="button" data-toggle="collapse" data-target="#form-msg" aria-expanded="false" aria-controls="form-message"><span class='octicon octicon-comment'></span></button>
</div>
</nav> <div class="row-fluid">
<div class="container">
<div class="collapse" id="form-msg">
<div class="card card-body">
<form action="/post" method="post" accept-charset="utf-8" enctype="application/x-www-form-urlencoded">
<div class="form-row">
<div class="col">
<input type="text" class="form-control" placeholder="Your name" name="name" required>
</div>
<div class="col">
<input type="text" class="form-control" placeholder="Message" name="msg" required>
<input type="text" class="form-control" name="url" id="url" hidden readonly>
</div>
<div class="col">
<select class="form-control" name="lvl">
<option value="info">Info</option>
<option value="warning">Warning</option>
<option value="danger">Disaster</option>
</select>
</div>
{% include "form.html" %}
<div class="col">
<button id="publish" name="save" class="btn btn-secondary">Publish <span class="octicon octicon-mail"></span></button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row-fluid">
<div class="container-fluid">
{% if check_servers %}
<table class='table table-borderless table-sm'>
{% for ip in check_servers%}
{% if check_servers[ip] != 0 %}
<tr class='bg-danger'>
<td class='text-center align-left'>
<span class='octicon octicon-alert'></span> Zabbix Server: {{ip}} seems unreachable! <span class='octicon octicon-alert'></span>
</td>
</tr>
{%endif%}
{%endfor%}
</table>
{%endif%}
{% if notes %}
<table class='table table-borderless'>
{% for note in notes %}
<tr class='bg-{{note["lvl"]}}'><td class='text-left'><span class='octicon octicon-clock'></span> {{note["date"]}}</td><td class='text-center'> {{note["msg"]}}</td><td class='text-right'><span class='octicon octicon-hubot'></span><i> (by {{note["name"]}})</i></td><td class='text-right'><form action='/del' method='post' accept-charset='utf-8' enctype='application/x-www-form-urlencoded'><input type='text' class='form-control url_note' name='url' hidden readonly><input type='text' name='note_id' value='{{note["ts"]}}' readonly hidden><button type='submit' class='btn btn-outline-light btn-sm' id='del_note' ><span class='octicon octicon-trashcan'></span></button></form></td></tr>"
{% endfor %}
</table>
{% endif %}
<table class="table table-dark table-sm alerts">
{% if alerts|length > 0 %}
{% for alert in alerts %}
{% if alert["priority"] == '5' %}
<tr class="disaster">
{% else %}
<tr>
{%endif%}
<td>
<span class='badge badge-pill' style='background-color:{{alert["color"]}}'>{{alert["hostgroup"]}}</span>
</td>
<td>{{alert["host"]}}</td>
<td>
<a href='{{zabbix_url}}/zabbix.php?action=problem.view&filter_set=1&filter_triggerids%5B%5D={{alert["triggerid"]}}' target='_blank' "+css_class+">{{alert["description"]}}</a>
</td>
{% if alert["priority"] == '1' %}
<td><span class="badge badge-pill badge-info">1</span></td>
{% elif alert["priority"] == '2' %}
<td><span class="badge badge-pill badge-primary">2</span></td>
{% elif alert["priority"] == '3' %}
<td><span class="badge badge-pill badge-warning">3</span></td>
{% elif alert["priority"] == '4' %}
<td><span class="badge badge-pill badge-danger">4</span></td>
{% elif alert["priority"] == '5' %}
<td><span class="octicon octicon-flame"> </span></td>
{% endif %}
<td>
<span class='badge badge-pill badge-light'>{{alert["since"]}}</span>
</td>
</tr>
{%endfor%}
{%else%}
<div class="container-fluid image-container"><img src="/images/zabbix_logo_500x131.png" width="30%"/></div>
{%endif%}
</table>
</div>
</div>
</body>
<script type="text/javascript">
var refresh;
$(document).ready(function() {
{% if tv_mode == true %}
$('nav').hide();
$('form').hide();
{% else %}
$('nav').show();
$('form').show();
{% endif %}
$('#url').val($(location).attr('href'));
$('.url_note').val($(location).attr('href'));
$('#del_note').click(function(e)
{
if(!confirm("This note will be deleted. Are you sure?"))
{
e.preventDefault();
}
});
function reload_page() {
if(Offline.state != 'up') {
clearInterval(refresh);
} else {
var refresh;
location.reload();
}
}
refresh = setInterval(reload_page,30000);
Offline.options = {
// to check the connection status immediatly on page load.
checkOnLoad: true,
// to monitor AJAX requests to check connection.
interceptRequests: true,
// to automatically retest periodically when the connection is down (set to false to disable).
reconnect: {
// delay time in seconds to wait before rechecking.
initialDelay: 3,
// wait time in seconds between retries.
delay: 10
},
// to store and attempt to remake requests which failed while the connection was down.
requests: true
};
});
</script>