first commit
This commit is contained in:
parent
95291c3fca
commit
5148756d7c
|
@ -0,0 +1,185 @@
|
|||
|
||||
CONTENT = {}
|
||||
LIST_SEVERITY = {
|
||||
'0': 'Not classified',
|
||||
'1': 'Information',
|
||||
'2': 'Warning',
|
||||
'3': 'Average',
|
||||
'4': 'High',
|
||||
'5': 'Disaster'
|
||||
}
|
||||
TOTAL_ALERTS = "0"
|
||||
|
||||
TEMPLATE_HEAD = '''<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>[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:white;
|
||||
}
|
||||
.table td, .table th { padding: 1px; !important }
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 15%;
|
||||
}
|
||||
.image-mexico {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 5%;
|
||||
}
|
||||
.btn-group-sm>.btn, .btn-sm {
|
||||
padding: .08rem .5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
NAVBAR
|
||||
<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>
|
||||
FORM_TEAM
|
||||
<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">
|
||||
NOTES
|
||||
</div>
|
||||
</div>-->
|
||||
<div class="row-fluid">
|
||||
<div class="container-fluid">
|
||||
CHECK
|
||||
NOTES
|
||||
<table class="table table-dark table-sm alerts">'''
|
||||
TEMPLATE_FOOTER='''
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
var refresh;
|
||||
$(document).ready(function() {
|
||||
$('nav').show();
|
||||
$('form').show();
|
||||
$('#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,20000);
|
||||
|
||||
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>
|
||||
</html>
|
||||
'''
|
||||
|
||||
COLOR_TEAM = {
|
||||
"Team-mon": "#04B404;",
|
||||
"Team-Infra": "#0000FF;",
|
||||
"Team-sys": "#FF0000;",
|
||||
"Team-datalake": "#FFBF00;",
|
||||
"Team-inge": "#088A4B;",
|
||||
"Team-CI": "#AEB404;",
|
||||
"Team-QA": "#FF00FF;",
|
||||
"Team-IT": "#006666;",
|
||||
"Team-top": "#1DD8EA;",
|
||||
"Team-RD": "#FE9A2E;",
|
||||
"Team-sec1": "#0B6138;",
|
||||
"Team-nocall": "#AC58FA;",
|
||||
"Team-nprd": "#0B610B;",
|
||||
"Team-exa": "#E7B339;",
|
||||
"Team-LM": "#DD90F2;",
|
||||
"Team-secu": "#F290E8;",
|
||||
"No-group": "#BDBDBD;"
|
||||
}
|
||||
|
||||
NAVBAR = '''
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">[0] 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" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
LIST
|
||||
</ul>
|
||||
<button class="btn btn-outline-secondary" type="button" data-toggle="collapse" data-target="#form-msg" aria-expanded="false" aria-controls="form-message">Add message <span class='octicon octicon-comment'></span></button>
|
||||
</div>
|
||||
</nav>
|
||||
'''
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,59 @@
|
|||
/* line 3, ../sass/_offline-theme-base-indicator.sass */
|
||||
.offline-ui, .offline-ui *, .offline-ui:before, .offline-ui:after, .offline-ui *:before, .offline-ui *:after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* line 6, ../sass/_offline-theme-base-indicator.sass */
|
||||
.offline-ui {
|
||||
display: none;
|
||||
position: fixed;
|
||||
background: white;
|
||||
z-index: 2000;
|
||||
display: inline-block;
|
||||
}
|
||||
/* line 13, ../sass/_offline-theme-base-indicator.sass */
|
||||
.offline-ui .offline-ui-retry {
|
||||
display: none;
|
||||
}
|
||||
/* line 16, ../sass/_offline-theme-base-indicator.sass */
|
||||
.offline-ui.offline-ui-up {
|
||||
display: block;
|
||||
}
|
||||
/* line 19, ../sass/_offline-theme-base-indicator.sass */
|
||||
.offline-ui.offline-ui-down {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* line 11, ../sass/offline-theme-default-indicator.sass */
|
||||
.offline-ui {
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-ms-border-radius: 4px;
|
||||
-o-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
font-family: "Helvetica Neue", sans-serif;
|
||||
padding: 1em;
|
||||
max-width: 100%;
|
||||
bottom: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
/* line 19, ../sass/offline-theme-default-indicator.sass */
|
||||
.offline-ui.offline-ui-up {
|
||||
background: #d6e9c6;
|
||||
color: #468847;
|
||||
}
|
||||
/* line 23, ../sass/offline-theme-default-indicator.sass */
|
||||
.offline-ui.offline-ui-down {
|
||||
background: #ec8787;
|
||||
color: #551313;
|
||||
}
|
||||
/* line 5, ../sass/offline-language-english-indicator.sass */
|
||||
.offline-ui.offline-ui-up .offline-ui-content:before {
|
||||
content: "Online";
|
||||
}
|
||||
/* line 10, ../sass/offline-language-english-indicator.sass */
|
||||
.offline-ui.offline-ui-down .offline-ui-content:before {
|
||||
content: "Offline";
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// DynaFav for Outscale Monitoring
|
||||
//
|
||||
// This is a dynamic favicon implementation for monitoring dashboards.
|
||||
//
|
||||
// Copyright 2020 Outscale SAS
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is furnished
|
||||
// to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// Get the Head tag.
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
|
||||
// Remove possible existing favicon link
|
||||
var link = document.querySelector("link[rel~=icon]");
|
||||
if (link)
|
||||
link.parentElement.removeChild(link);
|
||||
|
||||
// Create the Favicon item
|
||||
var el = document.createElement('link');
|
||||
el.type = 'image/x-icon';
|
||||
el.rel = 'icon';
|
||||
|
||||
// Create the Canvas to draw the icon.
|
||||
var canvas = document.createElement('canvas');
|
||||
|
||||
if (!canvas.getContext)
|
||||
return;
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// No need to size too big, browser will size down.
|
||||
canvas.height = canvas.width = 16; // set the size
|
||||
|
||||
var centerX = canvas.width / 2;
|
||||
var centerY = canvas.height / 2;
|
||||
var radius = centerX;
|
||||
|
||||
// Count the events we got, check if we got disasters ?
|
||||
var events = document.querySelectorAll("table.alerts > tbody > tr").length
|
||||
var disasters = document.querySelectorAll("table.alerts > tbody > tr.disaster").length
|
||||
|
||||
// Create the icon background with adequate icon/color
|
||||
var symbol = '√';
|
||||
ctx.fillStyle = 'green';
|
||||
if (disasters > 0) {
|
||||
ctx.fillStyle = 'red';
|
||||
symbol = '!';
|
||||
} else if (events > 0) {
|
||||
ctx.fillStyle = 'orange';
|
||||
symbol = '~';
|
||||
}
|
||||
|
||||
|
||||
// Draw the circle with fill color
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
|
||||
// Draw the symbol
|
||||
ctx.fillStyle = "white";
|
||||
ctx.textAlign = "center";
|
||||
ctx.font = 'bold 12px "helvetica", sans-serif';
|
||||
ctx.fillText(symbol, centerX, centerY + centerY / 2);
|
||||
|
||||
// Event count
|
||||
ctx.textAlign = "end";
|
||||
ctx.font = '11px "courier", monospace';
|
||||
// - Hard Shadow
|
||||
ctx.fillStyle = "rgba(255,255,255,0.7)";
|
||||
ctx.fillText(events, canvas.width - 2, canvas.height - 2);
|
||||
ctx.fillText(events, canvas.width - 2, canvas.height);
|
||||
ctx.fillText(events, canvas.width, canvas.height - 2);
|
||||
ctx.fillText(events, canvas.width, canvas.height);
|
||||
// - Actual count
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillOpacity = 1;
|
||||
ctx.fillText(events, canvas.width - 1, canvas.height - 1);
|
||||
|
||||
// Set link to data-href.
|
||||
el.href = canvas.toDataURL('image/png');
|
||||
|
||||
// Add the link to page head.
|
||||
head.appendChild(el);
|
||||
|
||||
return;
|
||||
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
|||
aiohttp==3.5.4
|
||||
async-timeout==3.0.1
|
||||
attrs==20.3.0
|
||||
chardet==3.0.4
|
||||
idna==2.10
|
||||
idna-ssl==1.1.0
|
||||
multidict==4.7.6
|
||||
typing-extensions==3.7.4.3
|
||||
yarl==1.6.3
|
||||
zabbix-api==0.5.5
|
|
@ -0,0 +1,418 @@
|
|||
from zabbix_api import ZabbixAPI
|
||||
from os import path
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import operator
|
||||
import aiohttp
|
||||
import aiohttp.web
|
||||
import json
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
import os.path
|
||||
import random
|
||||
import argparse
|
||||
import re
|
||||
import config
|
||||
import socket
|
||||
|
||||
|
||||
stdio_handler = logging.StreamHandler()
|
||||
stdio_handler.setLevel(logging.INFO)
|
||||
_logger = logging.getLogger('aiohttp.access')
|
||||
_logger.addHandler(stdio_handler)
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--zabbix_ip", help="Zabbix Frontend IP.", required=True)
|
||||
parser.add_argument("--zabbix_url", help="Zabbix 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):
|
||||
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"
|
||||
return time
|
||||
|
||||
def severity_badge(severity):
|
||||
if severity == 5:
|
||||
badge = '<span class="octicon octicon-flame"> </span>'
|
||||
elif severity == 4:
|
||||
badge = '<span class="badge badge-pill badge-danger">4</span>'
|
||||
elif severity == 3:
|
||||
badge = '<span class="badge badge-pill badge-warning">3</span>'
|
||||
elif severity == 2:
|
||||
badge = '<span class="badge badge-pill badge-primary">2</span>'
|
||||
elif severity == 1:
|
||||
badge = '<span class="badge badge-pill badge-info">1</span>'
|
||||
else:
|
||||
badge = '<span class="badge badge-pill badge-light">0</span>'
|
||||
return badge
|
||||
|
||||
def severity_css(severity):
|
||||
css = ""
|
||||
if severity == 5:
|
||||
css = 'class="disaster"'
|
||||
return css
|
||||
|
||||
def html_response(data):
|
||||
response = ""
|
||||
for x in range(len(data)):
|
||||
seconds = int(time.time()) - int(data[x]['lastchange'])
|
||||
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 capitalize_nth(s, n):
|
||||
if len(s) == 2:
|
||||
return s[:n].upper() + s[n:].capitalize()
|
||||
else:
|
||||
return s.title()
|
||||
|
||||
def construct_menu():
|
||||
MENU = ""
|
||||
|
||||
for key in config.CONTENT.keys():
|
||||
MENU += "<li class='nav-item'><a class='nav-link' href='/"+key+"'>"+ capitalize_nth(key.replace('Team-', ''),2) +"</a></li>"
|
||||
return MENU
|
||||
|
||||
def construct_form():
|
||||
FORM = "<div class='col'><select class='form-control' name='team'><option value='all'>All teams</option>"
|
||||
|
||||
for key in config.CONTENT.keys():
|
||||
FORM += "<option value='"+key+"'>"+key+"</option>"
|
||||
FORM += "</select></div>"
|
||||
return FORM
|
||||
|
||||
async def check_servers():
|
||||
global ZABBIX_SERVERS_CHECK
|
||||
|
||||
while True:
|
||||
_logger.info('[INFO] - Checking Zabbix Servers: {}'.format(ZABBIX_SERVERS_CHECK))
|
||||
j = {}
|
||||
for ip in 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 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')
|
||||
|
||||
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):
|
||||
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 show_alerts(request):
|
||||
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>'
|
||||
### EASTER EGG ###
|
||||
if teams != 'Team-sys':
|
||||
IMAGE='<div class="container-fluid image-container"><img src="/images/logo.png" width="50%"/></div>'
|
||||
else:
|
||||
IMAGE='<div class="container-fluid image-mexico"><img src="/images/patate.jpg" 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:
|
||||
try:
|
||||
zapi.logged_in()
|
||||
except Exception as e:
|
||||
_logger.info('[INFO] - {}: Connection to Zabbix API'.format(datetime.datetime.now()))
|
||||
zabbix_login()
|
||||
|
||||
request = dict()
|
||||
request['output'] = 'extend'
|
||||
request['search'] = dict()
|
||||
request['search']['name'] = [HOSTGROUP]
|
||||
request['searchWildcardsEnabled'] = 1
|
||||
resp = call_zabbix(request, 'hostgroup')
|
||||
groupids = []
|
||||
for x in range(len(resp)):
|
||||
config.CONTENT[resp[x]['name']] = []
|
||||
groupids.append(resp[x]['groupid'])
|
||||
|
||||
request = dict()
|
||||
request['limit'] = LIMIT
|
||||
request['groupids'] = groupids
|
||||
request['monitored'] = 1
|
||||
request['maintenance'] = 0
|
||||
request['active'] = 1
|
||||
request['min_severity'] = 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"]
|
||||
_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):
|
||||
app['dispatch'] = app.loop.create_task(process_zabbix_queue())
|
||||
app['dispatch'] = app.loop.create_task(check_servers())
|
||||
|
||||
|
||||
async def cleanup_background_tasks(app):
|
||||
app['dispatch'].cancel()
|
||||
await app['dispatch']
|
||||
|
||||
def main():
|
||||
|
||||
app = aiohttp.web.Application(logger=_logger)
|
||||
|
||||
app.add_routes([aiohttp.web.get('/', show_alerts),
|
||||
aiohttp.web.post('/post', post_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('/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=PORT)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue