From 3b0055c96fbec34cb07528fa05f3c0875d3a858d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20MOHIER?= Date: Sun, 16 Dec 2018 09:21:59 +0100 Subject: [PATCH] Alignak system menu with the live state and the events log --- module/helper.py | 155 +++++++ module/logevent.py | 317 ++++++++++++++ module/plugins/system/system.py | 386 +++++++++++++++++- .../plugins/system/views/alignak_events.tpl | 129 ++++++ module/plugins/system/views/alignak_ls.tpl | 14 + .../plugins/system/views/alignak_status.tpl | 78 ++++ module/views/header_element.tpl | 18 + 7 files changed, 1096 insertions(+), 1 deletion(-) create mode 100755 module/logevent.py create mode 100644 module/plugins/system/views/alignak_events.tpl create mode 100644 module/plugins/system/views/alignak_ls.tpl create mode 100644 module/plugins/system/views/alignak_status.tpl diff --git a/module/helper.py b/module/helper.py index 7f5b571f..7583e43e 100644 --- a/module/helper.py +++ b/module/helper.py @@ -749,5 +749,160 @@ def get_contact_avatar(self, contact, size=24, with_name=True, with_link=True): return s + def get_event_icon(self, event, disabled=False, label='', use_title=True): + ''' + Get an Html formatted string to display a monitoring event + + If disabled is True, the font used is greyed + + If label is empty, only an icon is returned + If label is set as 'state', the icon title is used as text + Else, the content of label is used as text near the icon. + + If use_title is False, do not include title attribute. + + Returns a span element containing a Font Awesome icon that depicts + consistently the event and its state + ''' + cls = event.get('type', 'unknown').lower() + state = event.get('state', 'n/a').upper() + state_type = event.get('state_type', 'n/a').upper() + hard = (state_type == 'HARD') + + # Icons depending upon element and real state ... + # ; History + icons = { + "unknown": { + "class": "history_Unknown", + "text": "Unknown event", + "icon": "question" + }, + + "retention_load": { + "class": "history_RetentionLoad", + "text": "Retention load", + "icon": "save" + }, + "retention_save": { + "class": "history_RetentionSave", + "text": "Retention save", + "icon": "save" + }, + + "alert": { + "class": "history_Alert", + "text": "Monitoring alert", + "icon": "bolt" + }, + + "notification": { + "class": "history_Notification", + "text": "Monitoring notification sent", + "icon": "paper-plane" + }, + + "check_result": { + "class": "history_CheckResult", + "text": "Check result", + "icon": "bolt" + }, + + "comment": { + "class": "history_WebuiComment", + "text": "WebUI comment", + "icon": "send" + }, + "timeperiod_transition": { + "class": "history_TimeperiodTransition", + "text": "Timeperiod transition", + "icon": "clock-o" + }, + "external_command": { + "class": "history_ExternalCommand", + "text": "External command", + "icon": "wrench" + }, + + "event_handler": { + "class": "history_EventHandler", + "text": "Monitoring event handler", + "icon": "bolt" + }, + "flapping_start": { + "class": "history_FlappingStart", + "text": "Monitoring flapping start", + "icon": "flag" + }, + "flapping_stop": { + "class": "history_FlappingStop", + "text": "Monitoring flapping stop", + "icon": "flag-o" + }, + "downtime_start": { + "class": "history_DowntimeStart", + "text": "Monitoring downtime start", + "icon": "ambulance" + }, + "downtime_cancelled": { + "class": "history_DowntimeCancelled", + "text": "Monitoring downtime cancelled", + "icon": "ambulance" + }, + "downtime_end": { + "class": "history_DowntimeEnd", + "text": "Monitoring downtime stopped", + "icon": "ambulance" + }, + "acknowledge_start": { + "class": "history_AckStart", + "text": "Monitoring acknowledge start", + "icon": "check" + }, + "acknowledge_cancelled": { + "class": "history_AckCancelled", + "text": "Monitoring acknowledge cancelled", + "icon": "check" + }, + "acknowledge_end": { + "class": "history_AckEnd", + "text": "Monitoring acknowledge expired", + "icon": "check" + }, + } + + back = '''''' \ + % (state.lower() if not disabled else 'greyed') + + icon_color = 'fa-inverse' + icon_style = "" + if not hard: + icon_style = 'style="opacity: 0.5"' + + try: + icon = icons[cls]['icon'] + title = icons[cls]['text'] + except KeyError: + cls = 'unknown' + icon = icons[cls]['icon'] + title = icons[cls]['text'] + + front = '''''' % (icon, icon_color) + + if use_title: + icon_text = '''%s%s''' % (icon_style, title, back, front) + else: + icon_text = '''%s%s''' % (icon_style, back, front) + + if label == '': + return icon_text + + color = state.lower() if not disabled else 'greyed' + if label == 'title': + label = title + return ''' + + %s %s + + ''' % (color, icon_text, label) helper = Helper() diff --git a/module/logevent.py b/module/logevent.py new file mode 100755 index 00000000..0bde6d62 --- /dev/null +++ b/module/logevent.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015-2016: Alignak team, see AUTHORS.txt file for contributors +# +# This file is part of Alignak. +# +# Alignak is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Alignak is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Alignak. If not, see . +# +# +# This file incorporates work covered by the following copyright and +# permission notice: +# +# Copyright (C) 2009-2014: +# Thibault Cohen, titilambert@gmail.com +# GrĂ©gory Starck, g.starck@gmail.com +# aviau, alexandre.viau@savoirfairelinux.com +# Sebastien Coavoux, s.coavoux@free.fr + +# This file is part of Shinken. +# +# Shinken is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Shinken is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Shinken. If not, see . +""" +This module lists provide facilities to parse log type Broks. +The supported event are listed in the event_type variable +""" + +import re + +# pylint: disable=bad-continuation +EVENT_TYPE_PATTERN = re.compile( + r'^(TIMEPERIOD TRANSITION|EXTERNAL COMMAND|RETENTION SAVE|RETENTION LOAD|' + r'CURRENT HOST STATE|CURRENT SERVICE STATE|HOST COMMENT|SERVICE COMMENT|' + r'HOST NOTIFICATION|SERVICE NOTIFICATION|HOST ALERT|SERVICE ALERT|' + r'HOST EVENT HANDLER|SERVICE EVENT HANDLER|ACTIVE HOST CHECK|ACTIVE SERVICE CHECK|' + r'PASSIVE HOST CHECK|PASSIVE SERVICE CHECK|HOST ACKNOWLEDGE ALERT|SERVICE ACKNOWLEDGE ALERT|' + r'HOST DOWNTIME ALERT|SERVICE DOWNTIME ALERT|HOST FLAPPING ALERT|SERVICE FLAPPING ALERT)' + r'($|: .*)' +) +EVENT_TYPES = { + 'TIMEPERIOD': { + # [1490998324] RETENTION SAVE + 'pattern': r'^(TIMEPERIOD) (TRANSITION): (.*)', + 'properties': [ + 'event_type', # 'TIMEPERIOD' + 'state_type', # 'TRANSITION' + 'output', + ] + }, + 'RETENTION': { + # [1490998324] RETENTION SAVE + 'pattern': r'^(RETENTION) (LOAD|SAVE): (.*)', + 'properties': [ + 'event_type', # 'RETENTION' + 'state_type', # 'LOAD' or 'SAVE' + 'output', # 'scheduler name + ] + }, + 'EXTERNAL': { + # [1490997636] EXTERNAL COMMAND: [1490997512] + # PROCESS_HOST_CHECK_RESULT;ek3022sg-0001;0;EK3022SG-0001 is alive, + # uptime is 43639 seconds (0 days 12 hours 7 minutes 19 seconds 229 ms)|'Uptime'=43639 + 'pattern': r'^(EXTERNAL COMMAND): (\[.*\]) (.*)$', + 'properties': [ + 'event_type', # 'EXTERNAL COMMAND' + 'timestamp', # '[1490997512]' + 'command', # 'PROCESS_SERVICE_CHECK_RESULT;ek3022sg-0001;svc_Screensaver;0;Ok|'ScreensaverOff'=61c + ] + }, + 'CURRENT': { + # ex: "[1498108167] CURRENT HOST STATE: localhost;UP;HARD;1;Host assumed to be UP" + # ex: "[1498108167] CURRENT SERVICE STATE: localhost;Maintenance;UNKNOWN;HARD;0;" + 'pattern': r'^CURRENT (HOST|SERVICE) (STATE): ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*);([^\;]*);([^\;]*)', + 'properties': [ + 'item_type', # 'SERVICE' (or could be 'HOST') + 'event_type', # 'STATE' + 'hostname', # 'localhost' + 'service_desc', # 'Maintenance' (or could be None) + 'state', # 'UP' + 'state_type', # 'HARD' + 'attempts', # '0' + 'output', # 'WARNING - load average: 5.04, 4.67, 5.04' + ] + }, + 'ACTIVE': { + # ex: "[1402515279] ACTIVE SERVICE CHECK: localhost;Nrpe-status;OK;HARD;1;NRPE v2.15" + 'pattern': r'^(ACTIVE) (HOST|SERVICE) (CHECK): ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*);([^\;]*);([^\;]*)', + 'properties': [ + 'check_type', # 'ACTIVE' + 'item_type', # 'SERVICE' (or could be 'HOST') + 'event_type', # 'CHECK' + 'hostname', # 'localhost' + 'service_desc', # 'cpu load maui' (or could be None) + 'state', # 'WARNING' + 'state_type', # 'HARD' + 'attempts', # '0' + 'output', # 'NRPE v2.15' + ] + }, + 'PASSIVE': { + # ex: "[1402515279] PASSIVE SERVICE CHECK: localhost;nsca_uptime;0;OK: uptime: 02:38h, + # boot: 2017-08-31 06:18:03 (UTC)|'uptime'=9508s;2100;90000" + 'pattern': r'^(PASSIVE) (HOST|SERVICE) (CHECK): ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^$]*)', + 'properties': [ + 'check_type', # 'PASSIVE' + 'item_type', # 'SERVICE' (or could be 'HOST') + 'event_type', # 'CHECK' + 'hostname', # 'localhost' + 'service_desc', # 'cpu load maui' (or could be None) + 'state_id', # '0' + 'output', # 'K: uptime: 02:38h, boot: 2017-08-31 06:18:03 (UTC) + # |'uptime'=9508s;2100;90000' + ] + }, + 'NOTIFICATION': { + # ex: "[1402515279] SERVICE NOTIFICATION: + # admin;localhost;check-ssh;CRITICAL;notify-service-by-email;Connection refused" + 'pattern': r'(HOST|SERVICE) (NOTIFICATION): ' + r'([^\;]*);([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*);([^\;]*)', + 'properties': [ + 'item_type', # 'SERVICE' (or could be 'HOST') + 'event_type', # 'NOTIFICATION' + 'contact', # 'admin' + 'hostname', # 'localhost' + 'service_desc', # 'check-ssh' (or could be None) + 'state', # 'CRITICAL' + 'notification_method', # 'notify-service-by-email' + 'output', # 'Connection refused' + ] + }, + 'ALERT': { + # ex: "[1329144231] SERVICE ALERT: + # dfw01-is02-006;cpu load maui;WARNING;HARD;4;WARNING - load average: 5.04, 4.67, 5.04" + 'pattern': r'^(HOST|SERVICE) (ALERT): ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*);([^\;]*);([^\;]*)', + 'properties': [ + 'item_type', # 'SERVICE' (or could be 'HOST') + 'event_type', # 'ALERT' + 'hostname', # 'localhost' + 'service_desc', # 'cpu load maui' (or could be None) + 'state', # 'WARNING' + 'state_type', # 'HARD' + 'attempts', # '4' + 'output', # 'WARNING - load average: 5.04, 4.67, 5.04' + ] + }, + 'EVENT': { + # ex: "[1329144231] HOST EVENT HANDLER: host-03;DOWN;HARD;0;g_host_event_handler" + 'pattern': r'^(HOST|SERVICE) (EVENT HANDLER): ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*);([^\;]*);([^\;]*)', + 'properties': [ + 'item_type', # 'SERVICE' (or could be 'HOST') + 'event_type', # 'EVENT HANDLER' + 'hostname', # 'localhost' + 'service_desc', # 'cpu load maui' (or could be None) + 'state', # 'WARNING' + 'state_type', # 'HARD' + 'attempts', # '4' + 'output', # 'g_host_event_handler' + ] + }, + 'COMMENT': { + # ex: "[1329144231] SERVICE COMMENT: + # dfw01-is02-006;cpu load maui;author;Comment text" + 'pattern': r'^(HOST|SERVICE) (COMMENT): ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^$]*)', + 'properties': [ + 'item_type', # 'SERVICE' (or could be 'HOST') + 'event_type', # 'COMMENT' + 'hostname', # 'localhost' + 'service_desc', # 'cpu load maui' (or could be None) + 'author', + 'comment', # 'WARNING - load average: 5.04, 4.67, 5.04' + ] + }, + 'ACKNOWLEDGE': { + # ex: "[1279250211] HOST ACKNOWLEDGE STARTED: + # maast64;Host has been acknowledged" + 'pattern': r'^(HOST|SERVICE) (ACKNOWLEDGE) ALERT: ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*)', + 'properties': [ + 'item_type', # 'SERVICE' or 'HOST' + 'event_type', # 'ACKNOWLEDGE' + 'hostname', # The hostname + 'service_desc', # The service description or None + 'state', # 'STARTED' or 'EXPIRED' or 'CANCELLED' + 'output', # 'Host has been acknowledged' + ] + }, + 'DOWNTIME': { + # ex: "[1279250211] HOST DOWNTIME ALERT: + # maast64;STARTED; Host has entered a period of scheduled downtime" + 'pattern': r'^(HOST|SERVICE) (DOWNTIME) ALERT: ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*)', + 'properties': [ + 'item_type', # 'SERVICE' or 'HOST' + 'event_type', # 'DOWNTIME' + 'hostname', # The hostname + 'service_desc', # The service description or None + 'state', # 'STARTED' or 'STOPPED' or 'CANCELLED' + 'output', # 'Service appears to have started flapping (24% change >= 20.0% threshold)' + ] + }, + 'FLAPPING': { + # service flapping ex: "[1375301662] SERVICE FLAPPING ALERT: + # testhost;check_ssh;STARTED; + # Service appears to have started flapping (24.2% change >= 20.0% threshold)" + + # host flapping ex: "[1375301662] HOST FLAPPING ALERT: + # hostbw;STARTED; Host appears to have started flapping (20.1% change > 20.0% threshold)" + 'pattern': r'^(HOST|SERVICE) (FLAPPING) ALERT: ' + r'([^\;]*);(?:([^\;]*);)?([^\;]*);([^\;]*)', + 'properties': [ + 'item_type', # 'SERVICE' or 'HOST' + 'event_type', # 'FLAPPING' + 'hostname', # The hostname + 'service_desc', # The service description or None + 'state', # 'STOPPED' or 'STARTED' + 'output', # 'Service appears to have started flapping (24% change >= 20.0% threshold)' + ] + } +} + +# SERVICE ACKNOWLEDGE ALERT: south_host_004;dummy_critical;STARTED; Service problem has been acknowledged + +class LogEvent(object): # pylint: disable=too-few-public-methods + """Class for parsing event logs + Populates self.data with the log type's properties + """ + + def __init__(self, log): + self.data = {} + self.syntax = False + self.valid = False + self.time = None + self.event_type = 'unknown' + self.pattern = 'unknown' + + # Find the type of event + event_type_match = EVENT_TYPE_PATTERN.match(log) + if not event_type_match: + return + self.syntax = True + + matched = event_type_match.group(1) + # print("Matched: %s" % matched) + matched = matched.split() + self.pattern = matched[0] + if self.pattern in ['HOST', 'SERVICE']: + self.pattern = matched[1] + # print("Pattern: %s" % self.pattern) + + # parse it with it's pattern + if self.pattern not in EVENT_TYPES: + return + + event_type = EVENT_TYPES[self.pattern] + properties_match = re.match(event_type['pattern'], log) + # print("Properties math: %s" % properties_match) + if not properties_match: + return + + self.valid = True + + # Populate self.data with the event's properties + for i, prop in enumerate(event_type['properties']): + self.data[prop] = properties_match.group(i + 1) + + # # Convert the time to int + # self.data['time'] = int(self.data['time']) + + # Convert event_type to int + if 'event_type' in self.data: + self.event_type = self.data['event_type'] + + # Convert attempts to int + if 'attempts' in self.data: + self.data['attempts'] = int(self.data['attempts']) + + def __iter__(self): + return iter(self.data.items()) + + def __len__(self): + return len(self.data) + + def __getitem__(self, key): + return self.data[key] + + def __contains__(self, key): + return key in self.data + + def __str__(self): + return str(self.data) diff --git a/module/plugins/system/system.py b/module/plugins/system/system.py index 91234029..a320abd9 100644 --- a/module/plugins/system/system.py +++ b/module/plugins/system/system.py @@ -30,6 +30,7 @@ import traceback from copy import deepcopy +from logevent import LogEvent from shinken.log import logger @@ -37,6 +38,383 @@ app = None +def _get_alignak_livesynthesis(): + """Get Alignak livesynthesis from the Arbiter API: + { + "alignak": "My Alignak", + "livesynthesis": { + "_overall": { + "_freshness": 1534237749, + "livesynthesis": { + "hosts_acknowledged": 0, + "hosts_down_hard": 0, + "hosts_down_soft": 0, + "hosts_flapping": 0, + "hosts_in_downtime": 0, + "hosts_not_monitored": 0, + "hosts_total": 13, + "hosts_unreachable_hard": 0, + "hosts_unreachable_soft": 0, + "hosts_up_hard": 13, + "hosts_up_soft": 0, + "services_acknowledged": 0, + "services_critical_hard": 6, + "services_critical_soft": 4, + "services_flapping": 0, + "services_in_downtime": 0, + "services_not_monitored": 0, + "services_ok_hard": 70, + "services_ok_soft": 0, + "services_total": 100, + "services_unknown_hard": 4, + "services_unknown_soft": 6, + "services_unreachable_hard": 0, + "services_unreachable_soft": 0, + "services_warning_hard": 5, + "services_warning_soft": 5 + } + }, + "scheduler-master": { + "_freshness": 1534237747, + "livesynthesis": { + "hosts_acknowledged": 0, + "hosts_down_hard": 0, + "hosts_down_soft": 0, + "hosts_flapping": 0, + "hosts_in_downtime": 0, + "hosts_not_monitored": 0, + "hosts_total": 13, + "hosts_unreachable_hard": 0, + "hosts_unreachable_soft": 0, + "hosts_up_hard": 13, + "hosts_up_soft": 0, + "services_acknowledged": 0, + "services_critical_hard": 6, + "services_critical_soft": 4, + "services_flapping": 0, + "services_in_downtime": 0, + "services_not_monitored": 0, + "services_ok_hard": 70, + "services_ok_soft": 0, + "services_total": 100, + "services_unknown_hard": 4, + "services_unknown_soft": 6, + "services_unreachable_hard": 0, + "services_unreachable_soft": 0, + "services_warning_hard": 5, + "services_warning_soft": 5 + } + } + }, + "name": "arbiter-master", + "running_id": "1534237614.73657398", + "start_time": 1534237614, + "type": "arbiter", + "version": "2.0.0rc2" + } + """ + if not getattr(app, 'alignak_endpoint', None): + logger.info("[WebUI-system] Alignak is not configured. Redirecting to the home page.") + app.bottle.redirect(app.get_url("Dashboard")) + + logger.debug("[WebUI-system] Get Alignak livesynthesis, endpoint: %s", app.alignak_endpoint) + try: + req = requests.Session() + raw_data = req.get("%s/livesynthesis" % app.alignak_endpoint) + data = json.loads(raw_data.content) + logger.debug("[WebUI-system] Result: %s", data) + except Exception as exp: + logger.error("[WebUI-system] alignak_livesynthesis, exception: %s", exp) + app.request.environ['MSG'] = "Alignak Error" + app.bottle.redirect(app.get_url("Dashboard")) + + return data + + +def _get_alignak_status(): + """Get Alignak overall status from the Arbiter API: + { + "livestate": { + "long_output": "broker-master - daemon is alive and reachable.\npoller-master - daemon is alive and reachable.\nreactionner-master - daemon is not reachable.\nreceiver-master - daemon is alive and reachable.\nscheduler-master - daemon is alive and reachable.", + "output": "Some of my daemons are not reachable.", + "perf_data": "'modules'=2 'timeperiods'=4 'services'=100 'servicegroups'=1 'commands'=10 'hosts'=13 'hostgroups'=5 'contacts'=2 'contactgroups'=2 'notificationways'=2 'checkmodulations'=0 'macromodulations'=0 'servicedependencies'=40 'hostdependencies'=0 'arbiters'=1 'schedulers'=1 'reactionners'=1 'brokers'=1 'receivers'=1 'pollers'=1 'realms'=1 'resultmodulations'=0 'businessimpactmodulations'=0 'escalations'=0 'hostsextinfo'=0 'servicesextinfo'=0", + "state": "up", + "timestamp": 1542611507 + }, + "name": "My Alignak", + "services": [ + { + "livestate": { + "long_output": "", + "output": "warning because some daemons are not reachable.", + "perf_data": "", + "state": "warning", + "timestamp": 1542611507 + }, + "name": "arbiter-master" + }, + { + "livestate": { + "long_output": "Realm: All (True). Listening on: http://127.0.0.1:7772/", + "name": "broker_broker-master", + "output": "daemon is alive and reachable.", + "perf_data": "last_check=0.00", + "state": "ok", + "timestamp": 1542611507 + }, + "name": "broker-master" + }, + { + "livestate": { + "long_output": "Realm: All (True). Listening on: http://127.0.0.1:7771/", + "name": "poller_poller-master", + "output": "daemon is alive and reachable.", + "perf_data": "last_check=0.00", + "state": "ok", + "timestamp": 1542611507 + }, + "name": "poller-master" + }, + { + "livestate": { + "long_output": "Realm: All (True). Listening on: http://127.0.0.1:7769/", + "name": "reactionner_reactionner-master", + "output": "daemon is not reachable.", + "perf_data": "last_check=0.00", + "state": "warning", + "timestamp": 1542611507 + }, + "name": "reactionner-master" + }, + { + "livestate": { + "long_output": "Realm: All (True). Listening on: http://127.0.0.1:7773/", + "name": "receiver_receiver-master", + "output": "daemon is alive and reachable.", + "perf_data": "last_check=0.00", + "state": "ok", + "timestamp": 1542611507 + }, + "name": "receiver-master" + }, + { + "livestate": { + "long_output": "Realm: All (True). Listening on: http://127.0.0.1:7768/", + "name": "scheduler_scheduler-master", + "output": "daemon is alive and reachable.", + "perf_data": "last_check=0.00", + "state": "ok", + "timestamp": 1542611507 + }, + "name": "scheduler-master" + } + ], + "template": { + "_templates": [ + "alignak", + "important" + ], + "active_checks_enabled": false, + "alias": "My Alignak", + "notes": "", + "passive_checks_enabled": true + }, + "variables": {} + } + """ + if not getattr(app, 'alignak_endpoint', None): + logger.info("[WebUI-system] Alignak is not configured. Redirecting to the home page.") + app.bottle.redirect(app.get_url("Dashboard")) + + logger.debug("[WebUI-system] Get Alignak status, endpoint: %s", app.alignak_endpoint) + try: + req = requests.Session() + raw_data = req.get("%s/status" % app.alignak_endpoint) + data = json.loads(raw_data.content) + logger.debug("[WebUI-system] Result: %s", data) + except Exception as exp: + logger.error("[WebUI-system] alignak_status, exception: %s", exp) + app.request.environ['MSG'] = "Alignak Error" + app.bottle.redirect(app.get_url("Dashboard")) + + return data + + +def alignak_status(): + """Alignak livestate view: + live state and live synthesis information from the arbiter""" + + return { + 'ls': _get_alignak_livesynthesis(), + 'status': _get_alignak_status() + } + + +def alignak_events(): + """Get Alignak Arbiter events: + """ + if not getattr(app, 'alignak_endpoint', None): + logger.info("[WebUI-system] Alignak is not configured. Redirecting to the home page.") + app.bottle.redirect(app.get_url("Dashboard")) + + user = app.request.environ['USER'] + _ = user.is_administrator() or app.redirect403() + + midnight_timestamp = time.mktime(datetime.date.today().timetuple()) + try: + range_start = int(app.request.query.get('range_start', midnight_timestamp)) + except ValueError: + range_start = midnight_timestamp + + try: + range_end = int(app.request.query.get('range_end', midnight_timestamp + 86399)) + except ValueError: + range_end = midnight_timestamp + 86399 + logger.debug("[WebUI-logs] get_global_history, range: %d - %d", range_start, range_end) + + # Apply search filter if exists ... + search = app.request.query.get('search', "type:host") + if "type:host" not in search: + search = "type:host " + search + logger.debug("[WebUI-system] search parameters '%s'", search) + + filters = ','.join(app.request.query.getall('filter')) or "" + logger.debug("[WebUI-system] filters: %s", filters) + + # Fetch elements per page preference for user, default is 25 + elts_per_page = app.prefs_module.get_ui_user_preference(user, 'elts_per_page', 25) + + # We want to limit the number of elements + step = int(app.request.query.get('step', elts_per_page)) + start = int(app.request.query.get('start', '0')) + end = int(app.request.query.get('end', start + step)) + + items = [] + for log in app.alignak_events: + # Try to get a monitoring event + try: + logger.debug("Log: %s", log) + event = LogEvent(log['message']) + logger.debug("-> event: %s", event) + if not event.valid: + logger.warning("No monitoring event detected from: %s", log['message']) + continue + + # ------------------------------------------- + data = deepcopy(log) + if event.event_type == 'RETENTION': + type = "retention_save" + if event.data['state_type'].upper() == 'LOAD': + type = "retention_load" + data.update({ + "type": type + }) + + if event.event_type == 'TIMEPERIOD': + data.update({ + "type": "timeperiod_transition", + }) + + if event.event_type == 'EXTERNAL COMMAND': + data.update({ + "type": "external_command", + "message": event.data['command'] + }) + + if event.event_type == 'ALERT': + data.update({ + "host_name": event.data['hostname'], + "service_name": event.data['service_desc'] or 'n/a', + "state": event.data['state'], + "state_type": event.data['state_type'], + "type": "alert", + }) + + if event.event_type == 'NOTIFICATION': + data.update({ + "host_name": event.data['hostname'], + "service_name": event.data['service_desc'] or 'n/a', + "type": "notification", + }) + + if event.event_type == 'DOWNTIME': + downtime_type = "downtime_start" + if event.data['state'] == 'STOPPED': + downtime_type = "downtime_end" + if event.data['state'] == 'CANCELLED': + downtime_type = "downtime_cancelled" + + data.update({ + "host_name": event.data['hostname'], + "service_name": event.data['service_desc'] or 'n/a', + "user_name": "Alignak", + "type": downtime_type, + }) + + if event.event_type == 'ACKNOWLEDGE': + ack_type = "acknowledge_start" + if event.data['state'] == 'EXPIRED': + ack_type = "acknowledge_end" + if event.data['state'] == 'CANCELLED': + ack_type = "acknowledge_cancelled" + + data.update({ + "host_name": event.data['hostname'], + "service_name": event.data['service_desc'] or 'n/a', + "user_name": "Alignak", + "type": ack_type, + }) + + if event.event_type == 'FLAPPING': + flapping_type = "monitoring.flapping_start" + if event.data['state'] == 'STOPPED': + flapping_type = "monitoring.flapping_stop" + + data.update({ + "host_name": event.data['hostname'], + "service_name": event.data['service_desc'] or 'n/a', + "user_name": "Alignak", + "type": flapping_type, + }) + + if event.event_type == 'COMMENT': + data.update({ + "host_name": event.data['hostname'], + "service_name": event.data['service_desc'] or 'n/a', + "user_name": event.data['author'] or 'Alignak', + "type": "comment", + }) + + if filters and data.get('type', 'unknown') not in filters: + continue + + items.append(data) + except ValueError: + logger.warning("Unable to decode a monitoring event from: %s", log['message']) + logger.warning(traceback.format_exc()) + continue + + # If we overflow, came back as normal + total = len(items) + if start > total: + start = 0 + end = step + + navi = app.helper.get_navi(total, start, step=step) + + logger.info("[WebUI-system] got %d matching items", len(items)) + + return { + 'navi': navi, + 'page': "alignak/events", + 'logs': items[start:end], + 'total': total, + "filters": filters, + 'range_start': range_start, + 'range_end': range_end + } + + def system_parameters(): user = app.request.environ['USER'] _ = user.is_administrator() or app.redirect403() @@ -153,5 +531,11 @@ def system_widget(): alignak_parameters: { 'name': 'AlignakParameters', 'route': '/alignak/parameters', 'view': 'alignak-parameters', 'static': True - } + }, + alignak_status: { + 'name': 'AlignakStatus', 'route': '/alignak/status', 'view': 'alignak_status' + }, + alignak_events: { + 'name': 'AlignakEvents', 'route': '/alignak/events', 'view': 'alignak_events' + }, } diff --git a/module/plugins/system/views/alignak_events.tpl b/module/plugins/system/views/alignak_events.tpl new file mode 100644 index 00000000..318f3e82 --- /dev/null +++ b/module/plugins/system/views/alignak_events.tpl @@ -0,0 +1,129 @@ +%rebase("layout", title='Alignak events log', css=['system/css/multiselect.css'], js=['system/js/multiselect.js'], breadcrumb=[ ['Alignak events log', '/alignak/events'] ]) + +%helper = app.helper + +
+
+
+
+

{{"%d total matching items" % total}}

+
+
+ +
+ %event_types = { "retention_load", "retention_save", "alert", "notification", "check_result", "webui_comment", "timeperiod_transition", "event_handler", "flapping_start", "flapping_stop", "downtime_start", "downtime_cancelled", "downtime_end", "acknowledge_start", "acknowledge_end" } + +
+
+
+ + +
+ + +
+ +
+
+ +
+
+ +
+ +
+
+
+ + %if not logs: + + %else: + + + + + + + + + + + + + %for log in logs: + + + + + + + + + %end + +
EventHostServiceMessage
{{log.get('date', '')}}{{! helper.get_event_icon(log)}} - {{log.get('type', '')}}{{log.get('host_name', '')}}{{log.get('service_name', '')}}{{log.get('message', '')}}
+ %end +
+ + +
diff --git a/module/plugins/system/views/alignak_ls.tpl b/module/plugins/system/views/alignak_ls.tpl new file mode 100644 index 00000000..d8ea8d86 --- /dev/null +++ b/module/plugins/system/views/alignak_ls.tpl @@ -0,0 +1,14 @@ +%rebase("layout", title='Alignak livesynthesis', css=['system/css/alignak.css'], js=['system/js/jquery.floatThead.min.js'], breadcrumb=[ ['Alignak livesynthesis', '/alignak/ls'] ]) + +%helper = app.helper + +
+
+ %if not ls: +
+

No live synthesis information is available.

+
+ %else: + %end +
+
diff --git a/module/plugins/system/views/alignak_status.tpl b/module/plugins/system/views/alignak_status.tpl new file mode 100644 index 00000000..3829ca57 --- /dev/null +++ b/module/plugins/system/views/alignak_status.tpl @@ -0,0 +1,78 @@ +%rebase("layout", title='Alignak livesynthesis', css=['system/css/alignak.css'], js=['system/js/jquery.floatThead.min.js'], breadcrumb=[ ['Alignak status', '/alignak/status'] ]) + +%helper = app.helper + +
+
+ %if not status: + + %else: + %livestate = status['livestate'] + %state = livestate.get('state', 'unknown').lower() +
+
+ {{!helper.get_fa_icon_state_and_label(cls="host", state=state)}} +
+
+

{{status['name']}}

+
+
+ + + + + + + + + + + + %for satellite in status['services']: + %livestate = satellite['livestate'] + %state = livestate.get('state', 'unknown').lower() + %last_check = livestate.get('timestamp', 0) + + + + + + + %end + +
SatelliteStateLast checkMessage
{{livestate.get('name', satellite['name'])}}{{!helper.get_fa_icon_state_and_label(cls="service", state=state, label=state)}}{{helper.print_duration(last_check, just_duration=True, x_elts=2)}}{{livestate.get('output', 'n/a')}}
+ + + + %columns = sorted(ls['livesynthesis']['_overall']['livesynthesis'].keys()) + + + + + + %for counter in columns: + + %end + + + + %for scheduler in ls['livesynthesis']: + + + %for counter in columns: + %cpt = ls['livesynthesis'][scheduler]['livesynthesis'][counter] + + %end + + %end + +
+
+ {{counter}} +
+
{{scheduler}}{{ cpt if cpt else '-' }}
+ %end +
+
diff --git a/module/views/header_element.tpl b/module/views/header_element.tpl index fcf5147a..a5163bfe 100644 --- a/module/views/header_element.tpl +++ b/module/views/header_element.tpl @@ -224,6 +224,7 @@ %if user.is_administrator():
  • + %else: +  Alignak + + %end