From 04c7473e5d106f92e13db67526aba3ac7b789e22 Mon Sep 17 00:00:00 2001 From: Jan Mach <jan.mach@cesnet.cz> Date: Fri, 5 Jan 2018 11:49:57 +0100 Subject: [PATCH] Implemented working prototype of report access module for Hawat. This implementation still needs lots of polishing and there are following known major issues: * invalid encoding of report message text coming from migrated MongoDB records * some parts of the interface are not working yet (report remailing, report deletion) * unauthorized access to reports is not working * report data browser is not yet implemented * report search result statistics are not yet implemented These issues will be resolved in separate commits. (Redmine issue: #3734) --- conf/core/reporting.json.conf | 8 + lib/hawat/base.py | 34 +++ .../design/templates/_macros_site.html | 42 +++- lib/hawat/blueprints/reports/__init__.py | 135 ++++++++++-- lib/hawat/blueprints/reports/forms.py | 51 +++++ .../reports/templates/reports/search.html | 94 ++++++-- .../reports/templates/reports/show.html | 202 ++++++++++++++++++ lib/hawat/const.py | 11 +- lib/mentat/const.py | 16 ++ 9 files changed, 552 insertions(+), 41 deletions(-) create mode 100644 conf/core/reporting.json.conf create mode 100644 lib/hawat/blueprints/reports/forms.py create mode 100644 lib/hawat/blueprints/reports/templates/reports/show.html diff --git a/conf/core/reporting.json.conf b/conf/core/reporting.json.conf new file mode 100644 index 000000000..a12b75663 --- /dev/null +++ b/conf/core/reporting.json.conf @@ -0,0 +1,8 @@ +{ + # + # Definitions of core configurations for event reporting. + # + "__core__reporting": { + "reports_dir": "/var/mentat/reports/reporter-ng" + } +} diff --git a/lib/hawat/base.py b/lib/hawat/base.py index 345f07a71..d30a2467e 100644 --- a/lib/hawat/base.py +++ b/lib/hawat/base.py @@ -231,6 +231,40 @@ class HawatFileView(HawatBaseView): ) +class HawatFileIdView(HawatBaseView): + """ + Base class for indirrect file access views. These views can be used to access + and serve files from arbitrary filesystem directories (that are accessible to + application process). This can be very usefull for serving files like charts, + that are periodically generated into configurable and changeable location. + """ + + @staticmethod + def get_directory_path(): + """ + *Hook method*. Must return full path to the directory, that will be used + as a base path for serving files. + """ + raise NotImplementedError() + + def get_filename(self, fileid, filetype): + """ + *Hook method*. + """ + raise NotImplementedError() + + def dispatch_request(self, fileid, filetype): + """ + Mandatory interface required by the :py:func:`flask.views.View.dispatch_request`. + Will be called by the *Flask* framework to service the request. + """ + return flask.send_from_directory( + self.get_directory_path(), + self.get_filename(fileid, filetype), + as_attachment=True + ) + + class HawatSimpleView(HawatBaseView): """ Base class for simple views. These are the most, well, simple views, that are diff --git a/lib/hawat/blueprints/design/templates/_macros_site.html b/lib/hawat/blueprints/design/templates/_macros_site.html index ee21c782b..1733839c0 100644 --- a/lib/hawat/blueprints/design/templates/_macros_site.html +++ b/lib/hawat/blueprints/design/templates/_macros_site.html @@ -375,7 +375,7 @@ {{ chitemkey }} </td> <td class="col-value"> - {{ babel_format_decimal(chdata[chitemkey]) }} + {{ babel_format_decimal(chdata.get(chitemkey, 0)) }} </td> <td> @@ -387,7 +387,7 @@ <tr> <th> </th> <th>Sum</th> - <th>{{ babel_format_decimal(chstats['sum_' + chstatkey]) }}</th> + <th>{{ babel_format_decimal(chstats.get('sum_' + chstatkey, 0)) }}</th> <th></th> </tr> </tfoot> @@ -398,23 +398,23 @@ <table class="table table-bordered table-striped table-condensed"> <tr> <th>{{ get_fa_icon('min') }} Min</th> - <td class="col-value">{{ babel_format_decimal(chstats['min_' + chstatkey]) }}</td> + <td class="col-value">{{ babel_format_decimal(chstats.get('min_' + chstatkey, 0)) }}</td> </tr> <tr> <th>{{ get_fa_icon('max') }} Max</th> - <td class="col-value">{{ babel_format_decimal(chstats['max_' + chstatkey]) }}</td> + <td class="col-value">{{ babel_format_decimal(chstats.get('max_' + chstatkey, 0)) }}</td> </tr> <tr> <th>{{ get_fa_icon('sum') }} Sum</th> - <td class="col-value">{{ babel_format_decimal(chstats['sum_' + chstatkey]) }}</td> + <td class="col-value">{{ babel_format_decimal(chstats.get('sum_' + chstatkey, 0)) }}</td> </tr> <tr> <th>{{ get_fa_icon('cnt') }} Cnt</th> - <td class="col-value">{{ babel_format_decimal(chstats['cnt_' + chstatkey]) }}</td> + <td class="col-value">{{ babel_format_decimal(chstats.get('cnt_' + chstatkey, 0)) }}</td> </tr> <tr> <th>{{ get_fa_icon('avg') }} Avg</th> - <td class="col-value">{{ babel_format_decimal(chstats['avg_' + chstatkey]) }}</td> + <td class="col-value">{{ babel_format_decimal(chstats.get('avg_' + chstatkey, 0)) }}</td> </tr> </table> @@ -422,3 +422,31 @@ </div> {%- endmacro %} + + +{#- ---------------------------------------------------------------------------- + + Macros for rendering event report related widgets. + +----------------------------------------------------------------------------- #} + + +{%- macro render_report_label_type(report) %} + {%- if report.type == 'summary' %} +<span class="label label-default" title="{{ gettext('Summary report') }}" data-toggle="tooltip">{{ get_fa_icon('r-t-summary') }}</span> + {%- elif report.type == 'extra' %} +<span class="label label-default" title="{{ gettext('Extra report') }}" data-toggle="tooltip">{{ get_fa_icon('r-t-extra') }}</span> + {%- endif %} +{%- endmacro %} + +{%- macro render_report_label_severity(report) %} + {%- if report.severity == 'low' %} +<span class="label label-default" title="{{ gettext('Low severity') }}" data-toggle="tooltip">{{ get_fa_icon('r-s-low') }}</span> + {%- elif report.severity == 'medium' %} +<span class="label label-info" title="{{ gettext('Medium severity') }}" data-toggle="tooltip">{{ get_fa_icon('r-s-medium') }}</span> + {%- elif report.severity == 'high' %} +<span class="label label-warning" title="{{ gettext('High severity') }}" data-toggle="tooltip">{{ get_fa_icon('r-s-high') }}</span> + {%- elif report.severity == 'critical' %} +<span class="label label-danger" title="{{ gettext('Critical severity') }}" data-toggle="tooltip">{{ get_fa_icon('r-s-critical') }}</span> + {%- endif %} +{%- endmacro %} diff --git a/lib/hawat/blueprints/reports/__init__.py b/lib/hawat/blueprints/reports/__init__.py index 1eeddb0cd..e8ad021b5 100644 --- a/lib/hawat/blueprints/reports/__init__.py +++ b/lib/hawat/blueprints/reports/__init__.py @@ -14,7 +14,7 @@ Hawat pluggable module: *reports* Description ^^^^^^^^^^^ -This pluggable module provides access to generated reports. +This pluggable module provides access to event reports. """ @@ -23,18 +23,23 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>" __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" +import collections + import flask import flask_login from flask_babel import lazy_gettext +import mentat.const +import mentat.stats.idea import hawat.base -import hawat.db + +from mentat.datatype.sqldb import EventReportModel +from hawat.blueprints.reports.forms import EventReportSearchForm -class SearchView(hawat.base.HawatBaseView): +class SearchView(hawat.base.HawatSearchView): decorators = [flask_login.login_required] - methods = ['GET'] @staticmethod def get_view_name(): @@ -56,16 +61,116 @@ class SearchView(hawat.base.HawatBaseView): def get_menu_title(): return lazy_gettext('Reports') - def get_template_context(self): - context = super().get_template_context() + @property + def dbmodel(self): + """ + *Hook property*. Implementation of :py:func:`hawat.base.HawatDbmodelView.dbmodel` interface. + """ + return EventReportModel + + @staticmethod + def get_search_form(args): + return EventReportSearchForm(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.dt_to.desc()).order_by(model.label.desc()).all() + + def do_before_render(self, items, context): + """ + *Hook method*. Will be called before rendering the template. + """ + #context.update( + # statistics = mentat.stats.idea.truncate_evaluations( + # mentat.stats.idea.aggregate_stat_groups(items) + # ) + #) + +class ShowView(hawat.base.HawatItemShowView): + """ + Detailed report view. + """ + + decorators = [flask_login.login_required] + methods = ['GET'] + + @staticmethod + def get_view_name(): + return 'show' + + @staticmethod + def get_view_title(): + return lazy_gettext('Show report') + + @staticmethod + def get_view_template(): + return 'reports/show.html' - return context + @staticmethod + def get_menu_icon(): + return 'reports' - def dispatch_request(self): - context = self.get_template_context() + @staticmethod + def get_menu_title(): + return lazy_gettext('Show report') - return flask.render_template(self.get_view_template(), **context) + #--------------------------------------------------------------------------- + @property + def dbmodel(self): + """ + *Hook property*. Implementation of :py:func:`hawat.base.HawatDbmodelView.dbmodel` interface. + """ + return EventReportModel + + +class UnauthShowView(ShowView): + """ + Unauthorized access to report detail view. + """ + pass + + +class DataView(hawat.base.HawatFileIdView): + + decorators = [flask_login.login_required] + + @staticmethod + def get_view_name(): + return 'data' + + @staticmethod + def get_view_title(): + return lazy_gettext('Event report data') + + @staticmethod + def get_menu_icon(): + return 'reports' + + @staticmethod + def get_menu_title(): + return lazy_gettext('Event report data') + + @staticmethod + def get_directory_path(): + return flask.current_app.mconfig[mentat.const.CKEY_CORE_REPORTING][mentat.const.CKEY_CORE_REPORTING_REPORTSDIR] + + def get_filename(self, fileid, filetype): + fileext = '' + if filetype == 'json': + fileext = 'json' + elif filetype == 'jsonzip': + fileext = 'json.zip' + elif filetype == 'csv': + fileext = 'csv' + elif filetype == 'csvzip': + fileext = 'csv.zip' + else: + raise ValueError("Requested invalid data file type '{}'".format(filetype)) + return 'security_report_{}.{}'.format(fileid, fileext) #------------------------------------------------------------------------------- @@ -74,7 +179,7 @@ class ReportsBlueprint(hawat.base.HawatBlueprint): @staticmethod def get_module_title(): - return lazy_gettext('Event reports pluggable module') + return lazy_gettext('Event report') def register_app(self, app): app.menu_main.add_entry('reports', content = self.hawat_view_list['search'], position = 120) @@ -89,12 +194,14 @@ def get_blueprint(): instance of :py:class:`hawat.base.HawatBlueprint` or :py:class:`flask.Blueprint`. """ - bp = ReportsBlueprint( + hbp = ReportsBlueprint( 'reports', __name__, template_folder = 'templates', url_prefix = '/reports') - bp.register_view_class(SearchView, '/search') + hbp.register_view_class(SearchView, '/search') + hbp.register_view_class(ShowView, '/<int:item_id>/show') + hbp.register_view_class(DataView, '/data/<fileid>/<filetype>') - return bp + return hbp diff --git a/lib/hawat/blueprints/reports/forms.py b/lib/hawat/blueprints/reports/forms.py new file mode 100644 index 000000000..bb2eb6d4f --- /dev/null +++ b/lib/hawat/blueprints/reports/forms.py @@ -0,0 +1,51 @@ +#!/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. +#------------------------------------------------------------------------------- + + +""" +This module contains custom internal geoip search form for Hawat. +""" + + +__author__ = "Jan Mach <jan.mach@cesnet.cz>" +__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" + + +import time +import datetime +import ipranges +import wtforms + +import flask_wtf +from flask_babel import lazy_gettext, gettext + +import hawat.forms + +class EventReportSearchForm(flask_wtf.FlaskForm): + """ + Class representing event report search form. + """ + dt_from = hawat.forms.DateTimeLocalField( + lazy_gettext('From:'), + validators = [ + wtforms.validators.Optional() + ], + format = '%Y-%m-%d %H:%M', + default = datetime.datetime.fromtimestamp(int(time.time() - (time.time() % 86400))) + ) + dt_to = hawat.forms.DateTimeLocalField( + lazy_gettext('To:'), + validators = [ + wtforms.validators.Optional() + ], + format = '%Y-%m-%d %H:%M' + ) + submit = wtforms.SubmitField( + lazy_gettext('Search') + ) diff --git a/lib/hawat/blueprints/reports/templates/reports/search.html b/lib/hawat/blueprints/reports/templates/reports/search.html index 79914970e..46681e49c 100644 --- a/lib/hawat/blueprints/reports/templates/reports/search.html +++ b/lib/hawat/blueprints/reports/templates/reports/search.html @@ -2,21 +2,81 @@ {% block content %} -<div class="row"> - <div class="col-lg-12"> - <h2>{{ hawat_view_title }}</h2> - - <form class="form-inline" id="frm-whi" method="get" action="{{ url_for('reports.search') }}"> - <div class="form-group"> - <label class="sr-only" for="frm-whi-search">Search local whois:</label> - <div class="input-group"> - <div data-toggle="tooltip" class="input-group-addon" title="Search local whois">{{ get_fa_icon('search') }}</div> - <input class="form-control" type="search" id="frm-whi-search" name="search" placeholder="IP address, range, network or abuse email" value="" size="40"> + <div class="row"> + <div class="col-lg-12"> + + <ol class="breadcrumb"> + <li><a href="{{ url_for('index') }}">{{ gettext('Home') }}</a></li> + <li class="active">{{ gettext('Reports') }}</li> + </ol> + <div class="jumbotron" style="margin-top: 1em;"> + <h2>{{ hawat_view_title }}</h2> + <hr> + <form method="GET" class="form-inline" action="{{ url_for('reports.search') }}"> + {{ macros_site.render_form_item_datetime(search_form.dt_from, 'datetimepicker-hm-from', False) }} + + {{ macros_site.render_form_item_datetime(search_form.dt_to, 'datetimepicker-hm-to', False) }} + + {{ search_form.submit(class_='btn btn-primary') }} + </form> + {%- if search_form.dt_from.errors %} + <div> + {{ macros_site.form_errors(search_form.dt_from.errors) }} + </div> + {%- endif %} + {%- if search_form.dt_to.errors %} + <div> + {{ macros_site.form_errors(search_form.dt_to.errors) }} + </div> + {%- endif %} + </div> <!-- /.jumbotron --> + + </div> <!-- /.col-lg-12 --> + </div> <!-- /.row --> + + {%- if items %} + + <div class="row"> + <div class="col-lg-12"> + + <div class="list-group"> + {%- for item in items %} + <a href="{{ url_for('reports.show', item_id = item.id ) }}" class="list-group-item"> + <h4 class="list-group-item-heading"> + {{ macros_site.render_report_label_type(item) }} + {{ macros_site.render_report_label_severity(item) }} + {{ item.label }} + {%- if item.flag_mailed %} + <span title="{{ item.mail_res }}, {{ babel_format_datetime(item.mail_dt) }}" data-toggle="tooltip">{{ get_fa_icon('mail') }}</span> + {%- endif %} + {%- if item.flag_testdata %} + <span title="{{ gettext('Report was generated from test data') }}" data-toggle="tooltip">{{ get_fa_icon('debug') }}</span> + {%- endif %} + </h4> + <p class="list-group-item-text"> + {{ gettext('Abuse group:') }} {{ item.group.name }} + | + {{ gettext('Report window:') }} {{ babel_format_datetime(item.dt_from) }} - {{ babel_format_datetime(item.dt_to) }} ({{ babel_format_timedelta(item.delta) }}) + | + {{ gettext('Created:') }} {{ babel_format_datetime(item.createtime) }} ({{ gettext('before') }} {{ babel_format_timedelta(current_datetime_utc - item.createtime) }}) + </p> + </a> + {%- endfor %} </div> - </div> - <input class="btn btn-primary" type="submit" name="submit" value="Search"> - </form> - </div> - <!-- /.col-lg-12 --> -</div> -{% endblock content %} + + </div> <!-- /.col-lg-12 --> + </div> <!-- /.row --> + + {%- if permission_can('developer') %} + + <hr> + +{{ macros_site.render_raw_item_view(items) }} + + {%- endif %} + + {%- else %} + <div class="alert alert-info">{{ gettext('No data matches your search criteria.') }}</div> + {%- endif %} + +{%- endblock content %} diff --git a/lib/hawat/blueprints/reports/templates/reports/show.html b/lib/hawat/blueprints/reports/templates/reports/show.html new file mode 100644 index 000000000..1d1cb8352 --- /dev/null +++ b/lib/hawat/blueprints/reports/templates/reports/show.html @@ -0,0 +1,202 @@ +{% extends "_layout.html" %} + +{% block content %} + + <div class="row"> + <div class="col-lg-12"> + + <ol class="breadcrumb"> + <li><a href="{{ url_for('index') }}">{{ gettext('Home') }}</a></li> + <li><a href="{{ url_for('reports.search') }}">{{ gettext('Reports') }}</a></li> + <li class="active">{{ gettext('Report detail') }}</li> + </ol> + <h2>{{ hawat_view_title }}</h2> + <hr> + <h3> + {{ item.label }} + {{ macros_site.render_report_label_type(item) }} + {{ macros_site.render_report_label_severity(item) }} + {%- if item.flag_mailed %} + <span class="label label-default" title="{{ item.mail_res }}, {{ babel_format_datetime(item.mail_dt) }}" data-toggle="tooltip">{{ get_fa_icon('mail') }}</span> + {%- endif %} + </h3> + {%- if item.flag_testdata %} + <p class="alert alert-warning">{{ get_fa_icon('debug') }} {{ gettext('This report was generated from test data.') }}</p> + {%- endif %} + <p> + <strong>{{ gettext('Target abuse group:')}}</strong> + {{ item.group.name }} + </p> + <p> + <strong>{{ gettext('Unprotected access link:')}}</strong> + {{ item.handle }} + </p> + <div class="pull-right"> + <div class="btn-toolbar" role="toolbar" aria-label="{{ gettext('Action toolbar') }}"> + <div class="btn-group" role="group" aria-label="{{ gettext('Action buttons') }}"> + <a data-toggle="tooltip" role="button" class="btn btn-default btn-sm" href="{{ url_for('reports.show', item_id = item.id ) }}" title="{{ gettext('Remail report "%(item)s"', item = item.label) }}">{{ get_fa_icon('mail') }} {{ gettext('Remail') }}</a> + <a data-toggle="tooltip" role="button" class="btn btn-default btn-sm" href="{{ url_for('reports.show', item_id = item.id ) }}" title="{{ gettext('Delete report "%(item)s"', item = item.label) }}">{{ get_fa_icon('trash') }} {{ gettext('Delete') }}</a> + <div class="btn-group" role="group"> + <a href="#" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> + {{ get_fa_icon('more') }} {{ gettext('More')}} <span class="caret"></span> + </a> + <ul class="dropdown-menu dropdown-menu-right"> + <li><a data-toggle="tooltip" href="{{ url_for('reports.data', fileid = item.label, filetype = 'json' ) }}" title="{{ gettext('Download data in JSON format for report "%(item)s"', item = item.label) }}">{{ get_fa_icon('save') }} {{ gettext('Download report data in JSON') }}</a></li> + <li><a data-toggle="tooltip" href="{{ url_for('reports.data', fileid = item.label, filetype = 'csv' ) }}" title="{{ gettext('Download data in CSV format for report "%(item)s"', item = item.label) }}">{{ get_fa_icon('save') }} {{ gettext('Download report data in CSV') }}</a></li> + <li role="separator" class="divider"></li> + <li><a data-toggle="tooltip" href="{{ url_for('reports.data', fileid = item.label, filetype = 'jsonzip' ) }}" title="{{ gettext('Download compressed data in JSON format for report "%(item)s"', item = item.label) }}">{{ get_fa_icon('save') }} {{ gettext('Download compressed report data in JSON') }}</a></li> + <li><a data-toggle="tooltip" href="{{ url_for('reports.data', fileid = item.label, filetype = 'csvzip' ) }}" title="{{ gettext('Download compressed data in CSV format for report "%(item)s"', item = item.label) }}">{{ get_fa_icon('save') }} {{ gettext('Download compressed report data in CSV') }}</a></li> + </ul> + </div> + </div> + </div> + </div> + <p> + <small> + {{ gettext('Report created:') }} {{ babel_format_datetime(item.createtime) }} ({{ gettext('before') }} {{ babel_format_timedelta(current_datetime_utc - item.createtime) }}) + | + {{ gettext('Report window:') }} {{ babel_format_datetime(item.dt_from) }} - {{ babel_format_datetime(item.dt_to) }} ({{ babel_format_timedelta(item.delta) }}) + </small> + </p> + </div> + </div> + + <div class="row"> + <div class="col-lg-12"> + + <!-- Nav tabs --> + <ul class="nav nav-tabs" role="tablist"> + <li role="presentation" class="active"> + <a href="#tab-report-message" aria-controls="tab-report-message" role="tab" data-toggle="tab"> + <strong>{{ gettext('Message') }}</strong> + </a> + </li> + <li role="presentation"> + <a href="#tab-report-metadata" aria-controls="tab-report-metadata" role="tab" data-toggle="tab"> + <strong>{{ gettext('Metadata') }}</strong> + </a> + </li> + <li role="presentation"> + <a href="#tab-report-stats" aria-controls="tab-report-stats" role="tab" data-toggle="tab"> + <strong>{{ gettext('Statistics') }}</strong> + </a> + </li> + <li role="presentation"> + <a href="#tab-report-data-json" aria-controls="tab-report-data-json" role="tab" data-toggle="tab"> + <strong>{{ gettext('Data') }}</strong> + </a> + </li> + </ul> + + <!-- Tab panes --> + <div class="tab-content"> + + <div role="tabpanel" class="tab-pane fade in active" id="tab-report-message"> + <samp> + {{ item.message | replace("\n", "<br/>\n") | replace(' ', ' ') | replace("\t", ' ') | safe }} + </samp> + </div> + + <div role="tabpanel" class="tab-pane fade" id="tab-report-metadata"> + <table class="table table-condensed table-striped"> + <tr> + <th>{{ gettext('Type:') }}</th> + <td>{{ gettext(item.type) }}</td> + </tr> + <tr> + <th>{{ gettext('Severity:') }}</th> + <td>{{ gettext(item.severity) }}</td> + </tr> + <tr> + <th>{{ gettext('Report window:') }}</th> + <td>{{ babel_format_datetime(item.dt_from) }} - {{ babel_format_datetime(item.dt_to) }} ({{ babel_format_timedelta(item.delta) }})</td> + </tr> + <tr> + <th>{{ gettext('Report delay:') }}</th> + <td>{{ babel_format_timedelta(item.createtime - item.dt_to) }}</td> + </tr> + <tr> + <th>{{ gettext('Event counts:') }}</th> + <td>{{ item.statistics['cnt_alerts'] }}{% if item.evcount_all %} ({{ item.evcount_all }} {{ gettext('matched') }}, {{ item.evcount_flt_blk }} {{ gettext('filtered out') }}, {{ item.evcount_thr_blk }} {{ gettext('thresholded') }}, {{ item.evcount_rlp }} {{ gettext('relapsed') }}){% endif %}</td> + </tr> + <tr> + <th>{{ gettext('IP address counts:') }}</th> + <td>{{ item.statistics['cnt_ips'] }}</td> + </tr> + {%- if item.flag_mailed %} + <tr> + <th>{{ gettext('Target mail:') }}</th> + <td>{{ item.mail_res }}, {{ babel_format_datetime(item.mail_dt) }}</td> + </tr> + {%- endif %} + </table> + </div> + + <div role="tabpanel" class="tab-pane fade" id="tab-report-stats"> + + <ul class="nav nav-tabs nav-tabs-tooltipped" role="tablist"> + {%- for chsection in ('analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %} + <li role="presentation" class="text-center{% if loop.first %} active{% endif %}"> + <a role="tab" data-toggle="tab" href="#tab-report-stats-{{ chsection }}" class="chart-tab"> + # {{ chsection }} + </a> + </li> + {%- endfor %} + </ul> + <div class="tab-content"> + {%- for chsection in ('analyzers', 'asns', 'categories', 'category_sets', 'countries', 'detectors', 'detectorsws', 'ips') %} + <div role="tabpanel" class="tab-pane fade{% if loop.first %} in active{% endif %}" id="tab-report-stats-{{ chsection }}"> + {{ macros_site.render_chart_pie( + 'report_stats', + item.statistics, + chsection, + 'Number of events per ' + chsection, + ) + }} + </div> + {%- endfor %} + </div> + </div> + + <div role="tabpanel" class="tab-pane fade" id="tab-report-data-json"> + <samp> + YET TO DO... + </samp> + </div> + + </div> + + </div> + </div> + +{% endblock content %} + +{%- block css %} +{{ super() }} + <link rel="stylesheet" href="{{ url_for('design.static', filename='vendor/nvd3/css/nv.d3.min.css') }}"> +{%- endblock css %} + + +{%- block headjs %} +{{ super() }} + <script src="{{ url_for('design.static', filename='vendor/d3/js/d3.min.js') }}"></script> + <script src="{{ url_for('design.static', filename='vendor/nvd3/js/nv.d3.min.js') }}"></script> +{%- endblock headjs %} + + +{%- block js %} +{{ super() }} + <script src="{{ url_for('design.static', filename='vendor/datatables/js/jquery.dataTables.js') }}"></script> + <script src="{{ url_for('design.static', filename='vendor/datatables/js/dataTables.bootstrap.js') }}"></script> + + <script> + $(document).ready(function() { + $('.hawat-datatable-nopaging').DataTable({ + "responsive": true, + "pageLength": 50, + "paging": false, + "ordering": true + }); + }); + </script> +{%- endblock js %} diff --git a/lib/hawat/const.py b/lib/hawat/const.py index 93a88eae3..2d13f1502 100644 --- a/lib/hawat/const.py +++ b/lib/hawat/const.py @@ -115,9 +115,14 @@ FA_ICONS = { 'modal-question': '<i class="fa fa-fw fa-question-circle"></i>', - 'briefs': '<i class="fa fa-fw fa-trophy"></i>', - 'rsummary': '<i class="fa fa-fw fa-archive" title="Summary report" data-toggle="tooltip"></i>', - 'rextra': '<i class="fa fa-fw fa-file" title="Extra report" data-toggle="tooltip"></i>', + 'r-t-summary': '<i class="fa fa-fw fa-archive"></i>', + 'r-t-extra': '<i class="fa fa-fw fa-file"></i>', + 'r-s-low': '<i class="fa fa-fw fa-thermometer-1"></i>', + 'r-s-medium': '<i class="fa fa-fw fa-thermometer-2"></i>', + 'r-s-high': '<i class="fa fa-fw fa-thermometer-3"></i>', + 'r-s-critical': '<i class="fa fa-fw fa-thermometer-4"></i>', + + 'debug': '<i class="fa fa-fw fa-bug"></i>', 'eventclss': '<i class="fa fa-fw fa-book"></i>', 'reference': '<i class="fa fa-fw fa-external-link"></i>', 'anchor': '<i class="fa fa-fw fa-anchor"></i>', diff --git a/lib/mentat/const.py b/lib/mentat/const.py index 5be08c059..7fbea3dab 100644 --- a/lib/mentat/const.py +++ b/lib/mentat/const.py @@ -55,6 +55,18 @@ CKEY_CORE_SERVICES_WHOIS = 'whois' #------------------------------------------------------------------------------- +CKEY_CORE_REPORTING = '__core__reporting' +"""Name of the configuration key for ``core reporting`` configurations.""" +CKEY_CORE_REPORTING_REPORTSDIR = 'reports_dir' +"""Name of the configuration subkey key for ``reports dir`` configuration in ``core reporting`` configurations.""" + +#------------------------------------------------------------------------------- + +EVENT_SEVERITY_LOW = 'low' +EVENT_SEVERITY_MEDIUM = 'medium' +EVENT_SEVERITY_HIGH = 'high' +EVENT_SEVERITY_CRITICAL = 'critical' + REPORTING_MODE_SUMMARY = 'summary' REPORTING_MODE_EXTRA = 'extra' REPORTING_MODE_BOTH = 'both' @@ -112,3 +124,7 @@ REPORTING_INTERVALS = { } REPORTING_INTERVALS_INV = {v: k for k, v in REPORTING_INTERVALS.items()} + +REPORT_TYPES = (REPORTING_MODE_SUMMARY, REPORTING_MODE_EXTRA) + +REPORT_SEVERITIES = (EVENT_SEVERITY_LOW, EVENT_SEVERITY_MEDIUM, EVENT_SEVERITY_HIGH, EVENT_SEVERITY_CRITICAL) -- GitLab