__init__.py 16.05 KiB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# This file is part of Mentat system (https://mentat.cesnet.cz/).
#
# Copyright (C) since 2011 CESNET, z.s.p.o (http://www.ces.net/)
# Use of this source is governed by the MIT license, see LICENSE file.
#-------------------------------------------------------------------------------
"""
Description
-----------
This pluggable module provides access to IDEA message database.
"""
__author__ = "Jan Mach <jan.mach@cesnet.cz>"
__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
import re
import flask
from flask_babel import gettext, lazy_gettext
import mentat.stats.idea
import mentat.services.eventstorage
from mentat.datatype.sqldb import EventStatisticsModel
import hawat.const
import hawat.base
import hawat.events
import hawat.acl
from hawat.blueprints.events.forms import SimpleEventSearchForm, EventDashboardForm
#
# Name of the blueprint as module global constant.
#
BLUEPRINT_NAME = 'events'
def in_query_params(haystack, needles, on_true = True, on_false = False, on_empty = False):
"""
Utility method for checking that any needle from given list of needles is
present in given haystack.
"""
if not haystack:
return on_empty
for needle in needles:
if needle in haystack:
return on_true
return on_false
class SearchView(hawat.base.HawatRenderableView):
"""
View responsible for searching IDEA event database and presenting the results.
"""
methods = ['GET']
authentication = True
authorization = [hawat.acl.PERMISSION_ANY]
@classmethod
def get_view_name(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_view_name`.
"""
return 'search'
@classmethod
def get_menu_icon(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_icon`.
"""
return 'module-{}'.format(BLUEPRINT_NAME)
@classmethod
def get_menu_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_title`.
"""
return lazy_gettext('Events')
@classmethod
def get_view_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatRenderableView.get_view_title`.
"""
return lazy_gettext('Search IDEA event database')
@classmethod
def get_query_parameters(cls, form, request_args):
"""
This method can be used to get the dictionary containing actual parameters
that were used to generate and send the request. This dictionary can be
then used to generate for example paginator links.
:param wtforms.Form form: Form that was parsed from raw request.
:param dict request_args: Raw request arguments.
:return: Dictionary containing intersect of both form and request_args
:rtype: dict
"""
params = {}
for arg in request_args:
if getattr(form, arg, None) and request_args.get(arg, None):
params[arg] = request_args[arg]
return params
@classmethod
def search(cls, args, context):
"""
Perform actual search of IDEA events using provided query arguments.
:param dict args: Search query arguments.
:param dict context: Jinja2 template context, that will be passed to template during rendering.
:return: Tuple containing number of items as integer and list of searched items.
:rtype: tuple
"""
items_count_total, items = hawat.events.db_get().search_events(args)
context.update(sqlquery = hawat.events.db_get().cursor.lastquery)
return items_count_total, items
@classmethod
def get_context_action_menu(cls):
"""
Get context action menu for particular item.
"""
action_menu = hawat.menu.HawatMenu()
action_menu.add_entry(
'endpoint',
'show',
endpoint = 'events.show',
hidetitle = True,
legend = lambda x: lazy_gettext('View details of event "%(item)s"', item = x.get_id()),
item_kw = 'get_id'
)
action_menu.add_entry(
'endpoint',
'download',
endpoint = 'events.download',
icon = 'action-download',
hidetitle = True,
legend = lambda x: lazy_gettext('Download event "%(item)s"', item = x.get_id()),
item_kw = 'get_id'
)
return action_menu
def dispatch_request(self): # pylint: disable=locally-disabled,too-many-locals
"""
Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
Will be called by the *Flask* framework to service the request.
"""
# Get lists of available options for various event search form select fields.
sourcetypes = hawat.events.get_event_source_types()
targettypes = hawat.events.get_event_target_types()
hosttypes = sorted(list(set(sourcetypes + targettypes)))
detectors = hawat.events.get_event_detectors()
detectortypes = hawat.events.get_event_detector_types()
categories = hawat.events.get_event_categories()
severities = hawat.events.get_event_severities()
classes = hawat.events.get_event_classes()
protocols = hawat.events.get_event_protocols()
form = SimpleEventSearchForm(
flask.request.args,
meta = {'csrf': False},
choices_source_types = list(zip(sourcetypes,sourcetypes)),
choices_target_types = list(zip(targettypes,targettypes)),
choices_host_types = list(zip(hosttypes,hosttypes)),
choices_detectors = list(zip(detectors,detectors)),
choices_detector_types = list(zip(detectortypes,detectortypes)),
choices_categories = list(zip(categories,categories)),
choices_severities = list(zip(severities,severities)),
choices_classes = list(zip(classes,classes)),
choices_protocols = list(zip(protocols,protocols))
)
context = self.get_template_context()
if hawat.const.HAWAT_FORM_ACTION_SUBMIT in flask.request.args:
if form.validate():
form_data = form.data
form_data['groups'] = [item.name for item in form_data['groups']]
try:
items_count_total, items = self.search(form_data, context)
context.update(
searched = True,
items = items,
items_count = len(items),
items_count_total = items_count_total,
pager_index_low = ((form_data['page'] - 1) * form_data['limit']) + 1,
pager_index_high = ((form_data['page'] - 1) * form_data['limit']) + len(items),
pager_index_limit = ((form_data['page'] - 1) * form_data['limit']) + form_data['limit'],
form_data = form_data,
context_action_menu = self.get_context_action_menu()
)
except mentat.services.eventstorage.DataError as err:
match = re.match('invalid IP4R value: "([^"]+)"', str(err))
if match:
flask.flash(
flask.Markup(
gettext(
'Invalid address value <strong>%(address)s</strong> in event search form.',
address = flask.escape(str(match.group(1)))
)
),
hawat.const.HAWAT_FLASH_FAILURE
)
else:
raise
context.update(
search_form = form,
request_args = flask.request.args,
query_params = self.get_query_parameters(form, flask.request.args),
in_query_params = in_query_params
)
return flask.render_template(self.get_view_template(), **context)
class ShowView(hawat.base.HawatRenderableView):
"""
Detailed event view.
"""
methods = ['GET']
authentication = True
authorization = [hawat.acl.PERMISSION_ANY]
@classmethod
def get_view_name(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_view_name`.
"""
return 'show'
@classmethod
def get_menu_icon(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_icon`.
"""
return 'action-show'
@classmethod
def get_menu_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_title`.
"""
return lazy_gettext('Show IDEA event')
@classmethod
def get_view_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatRenderableView.get_view_title`.
"""
return lazy_gettext('Show IDEA event')
@classmethod
def get_action_menu(cls, item_id, item): # pylint: disable=locally-disabled,unused-argument
"""
Get action menu for particular item.
"""
action_menu = hawat.menu.HawatMenu()
action_menu.add_entry(
'endpoint',
'download',
endpoint = 'events.download',
title = lazy_gettext('Download'),
icon = 'action-download',
legend = lazy_gettext('Download event "%(item)s"', item = item_id),
link = flask.url_for('events.download', item_id = item_id)
)
return action_menu
#---------------------------------------------------------------------------
def dispatch_request(self, item_id): # pylint: disable=locally-disabled,arguments-differ
"""
Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
Will be called by the *Flask* framework to service the request.
Single item with given unique identifier will be retrieved from database
and injected into template to be displayed to the user.
"""
item = hawat.events.db_get().fetch_event(item_id)
if not item:
flask.abort(404)
context = self.get_template_context()
context.update(
item_id = item_id,
item = item,
action_menu = self.get_action_menu(item_id, item)
)
return flask.render_template(self.get_view_template(), **context)
class DownloadView(hawat.base.HawatBaseView):
"""
Download event view.
"""
methods = ['GET']
authentication = True
authorization = [hawat.acl.PERMISSION_ANY]
@classmethod
def get_view_name(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_view_name`.
"""
return 'download'
@classmethod
def get_menu_icon(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_icon`.
"""
return 'action-download'
@classmethod
def get_menu_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_title`.
"""
return lazy_gettext('Download IDEA event')
#---------------------------------------------------------------------------
def dispatch_request(self, item_id): # pylint: disable=locally-disabled,arguments-differ
"""
Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`.
Will be called by the *Flask* framework to service the request.
Single item with given unique identifier will be retrieved from database
and injected into template to be displayed to the user.
"""
item = hawat.events.db_get().fetch_event(item_id)
if not item:
flask.abort(404)
response = flask.make_response(
item.to_json(indent = 4, sort_keys = True)
)
response.mimetype = 'application/json'
response.headers['Content-Disposition'] = 'attachment; filename={}.idea.json'.format(item_id)
return response
class DashboardView(hawat.base.HawatSearchView):
"""
View responsible for presenting overall system performance dashboard.
"""
authentication = True
authorization = [hawat.acl.PERMISSION_ANY]
@classmethod
def get_view_name(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_view_name`.
"""
return 'dashboard'
@classmethod
def get_menu_icon(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_icon`.
"""
return 'module-{}'.format(BLUEPRINT_NAME)
@classmethod
def get_menu_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBaseView.get_menu_title`.
"""
return lazy_gettext('Events')
@classmethod
def get_view_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatRenderableView.get_view_title`.
"""
return lazy_gettext('Overall event dashboards')
@property
def dbmodel(self):
"""
*Interface implementation* of :py:func:`hawat.base.HawatDbmodelView.dbmodel`.
"""
return EventStatisticsModel
@staticmethod
def get_search_form(args):
return EventDashboardForm(args, meta = {'csrf': False})
def search(self, query, model, args, context):
if 'dt_from' in args and args['dt_from']:
query = query.filter(model.dt_from >= args['dt_from'])
if 'dt_to' in args and args['dt_to']:
query = query.filter(model.dt_to <= args['dt_to'])
return query.order_by(model.interval).all()
def do_before_render(self, item, context):
"""
*Hook method*. Will be called before rendering the template.
"""
context.update(
statistics = mentat.stats.idea.truncate_evaluations(
mentat.stats.idea.aggregate_stat_groups(item)
)
)
#-------------------------------------------------------------------------------
class EventsBlueprint(hawat.base.HawatBlueprint):
"""
Hawat pluggable module - IDEA events.
"""
@classmethod
def get_module_title(cls):
"""
*Interface implementation* of :py:func:`hawat.base.HawatBlueprint.get_module_title`.
"""
return lazy_gettext('IDEA events pluggable module')
def register_app(self, app):
"""
*Callback method*. Will be called from :py:func:`hawat.base.HawatApp.register_blueprint`
method and can be used to customize the Flask application object. Possible
use cases:
* application menu customization
:param hawat.base.HawatApp app: Flask application to be customize.
"""
app.menu_main.add_entry(
'view',
'dashboards.events',
position = 10,
view = DashboardView
)
app.menu_main.add_entry(
'view',
BLUEPRINT_NAME,
position = 140,
view = SearchView,
resptitle = True
)
#-------------------------------------------------------------------------------
def get_blueprint():
"""
Mandatory interface and factory function. This function must return a valid
instance of :py:class:`hawat.base.HawatBlueprint` or :py:class:`flask.Blueprint`.
"""
hbp = EventsBlueprint(
BLUEPRINT_NAME,
__name__,
template_folder = 'templates',
url_prefix = '/{}'.format(BLUEPRINT_NAME)
)
hbp.register_view_class(SearchView, '/search')
hbp.register_view_class(ShowView, '/show/<item_id>')
hbp.register_view_class(DownloadView, '/download/<item_id>')
hbp.register_view_class(DashboardView, '/dashboard')
return hbp