Skip to content
Snippets Groups Projects
Commit 637bb867 authored by Jan Zerdik's avatar Jan Zerdik
Browse files

Feedback for reports.

Lightweight feedback functionality for reports. Feedback is send to addresses from config file with information about logged user. Without feedback for users that are not logged-in. Feedback info is not stored permanently. (Redmine issue: #4501)
parent fe828cd1
No related branches found
No related tags found
No related merge requests found
......@@ -10,5 +10,8 @@ SECRET_KEY = '!!!-CHANGE-ME-ASAP-!!!-local-secret-key'
# Define list of Hawat administrator accounts.
#HAWAT_ADMINS = ['boss@domain.org', 'admin@domain.org']
# Define list of Hawat administrator accounts, that receive feedback messages for reports.
#HAWAT_REPORT_FEEDBACK_MAILS = ['admin@domain.org']
# Directories with translations used in hawat
BABEL_TRANSLATION_DIRECTORIES = 'translations;/etc/mentat/templates/reporter/translations'
......@@ -23,6 +23,7 @@ __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea
import datetime
import pytz
import json
#
# Flask related modules.
......@@ -30,7 +31,8 @@ import pytz
import flask
import flask_login
import flask_principal
from flask_babel import gettext, lazy_gettext
import flask_mail
from flask_babel import gettext, lazy_gettext, force_locale
from jinja2.loaders import ChoiceLoader, FileSystemLoader
from flask.helpers import locked_cached_property
import os.path
......@@ -46,7 +48,7 @@ import hawat.menu
import hawat.acl
from hawat.base import HTMLMixin, SQLAlchemyMixin, BaseSearchView,\
ItemShowView, ItemDeleteView, FileIdView, HawatBlueprint,\
URLParamsBuilder
URLParamsBuilder, BaseView
from hawat.blueprints.reports.forms import EventReportSearchForm, ReportingDashboardForm
......@@ -526,6 +528,62 @@ class DeleteView(HTMLMixin, SQLAlchemyMixin, ItemDeleteView): # pylint: disable
return gettext('Canceled deleting event report <strong>%(item_id)s</strong>.', item_id = str(kwargs['item']))
class FeedbackView(BaseView):
"""
View for sending feedback for reports.
"""
methods = ['POST']
authentication = True
authorization = [hawat.acl.PERMISSION_ANY]
@classmethod
def get_view_name(cls):
"""*Implementation* of :py:func:`hawat.base.BaseView.get_view_name`."""
return 'feedback'
#---------------------------------------------------------------------------
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.
Feedback for report with label *item_id*.
More specific part like section and ip can be send in POST data.
"""
mail_locale = flask.current_app.config['BABEL_DEFAULT_LOCALE']
data = flask.request.get_json()
feedback_for = item_id
link = flask.current_app.mconfig[mentat.const.CKEY_CORE_REPORTER][mentat.const.CKEY_CORE_REPORTER_TEMPLATEVARS]["report_access_url"] + item_id + "/unauth"
if data.get("section"):
feedback_for += " (" + data["section"]
link += "#" + data["section"]
if data.get("ip"):
feedback_for += ", ip: " + data["ip"]
feedback_for += ")"
with force_locale(mail_locale):
msg = flask_mail.Message(
gettext(
"[Mentat] Feedback for report - %(item_id)s",
item_id=item_id
),
recipients=flask.current_app.config['HAWAT_REPORT_FEEDBACK_MAILS'],
reply_to=flask_login.current_user.email
)
msg.body = flask.render_template(
'reports/email_report_feedback.txt',
account=flask_login.current_user,
feedback=data["feedback"],
feedback_for=feedback_for,
link=link
)
flask.current_app.mailer.send(msg)
response = flask.make_response()
return response
#-------------------------------------------------------------------------------
......@@ -609,5 +667,6 @@ def get_blueprint():
hbp.register_view_class(DataView, '/data/<fileid>/<filetype>')
hbp.register_view_class(DashboardView, '/dashboard')
hbp.register_view_class(DeleteView, '/<int:item_id>/delete')
hbp.register_view_class(FeedbackView, '/<item_id>/feedback')
return hbp
{{ _('Dear administrator,') | wordwrap }}
{{ _('Feedback for report "%(feedback_for)s" has been send by user:', feedback_for = feedback_for) | wordwrap }}
{{ '{:16s}'.format(_('Login:')) }} {{ account.login }}
{{ '{:16s}'.format(_('Full name:')) }} {{ account.fullname }}
{{ '{:16s}'.format(_('Email:')) }} {{ account.email }}
{{ '{:16s}'.format(_('Organization:')) }} {{ account.organization }}
{{ _('Message by user:') | wordwrap }}
{{ feedback | wordwrap(width=75, break_long_words=False) | indent(width=4, indentfirst=True) }}
{{ _('Link to report:') | wordwrap }}
{{ link }}
{{ _('Have a nice day') | wordwrap }}
{{ _('-- Mentat System') | wordwrap }}
......@@ -110,25 +110,43 @@
{% import '_report.references.txt.j2' as references %}
{% import '_macros.common.txt.j2' as macros_common %}
{%- macro render_report_section(section_number, section_name, section_data) %}
<div> {{ '[' ~ section_number ~ '] ' ~ labels.render_section_label(section_name, none) }} </div>
<a name="{{ section_name }}"></a><div> {{ '[' ~ section_number ~ '] ' ~ labels.render_section_label(section_name, none) }} </div>
<div class="report-message" style="margin-left:2em"">
<table class="table table-striped">
<table class="table table-condensed">
<thead>
<th> {{ gettext('Source') }} </th>
<th> {{ gettext('First event time') }} </th>
<th> {{ gettext('Last event time') }} </th>
<th style="text-align:right"> {{ gettext('Count') }} </th>
<th> {{ gettext('Protocol') }} </th>
<th>{{ gettext('Source') }}</th>
<th>{{ gettext('First event time') }}</th>
<th>{{ gettext('Last event time') }}</th>
<th style="text-align:right">{{ gettext('Count') }}</th>
<th>{{ gettext('Protocol') }}</th>
{%- if current_user.is_authenticated %}
<th>{{ gettext('Feedback') }}</th>
{%- endif %}
</thead>
<tbody>
{%- for ip in section_data | dictsort %}
{% set id = section_name + '-' + ip[0] %}
<tr>
<td> {{ ip[0] }} </td>
<td> {{ ip[1]['first_time'] }} </td>
<td> {{ ip[1]['last_time'] }} </td>
<td style="text-align:right"> {{ ip[1]['count'] }} </td>
<td> {% if ip[1]['ports'] %}{{ ', '.join(ip[1]['ports']) }}{% else %}---{% endif %} </td>
<td id="{{ 'ip-' + id }}">{{ ip[0] }}</td>
<td>{{ ip[1]['first_time'] }}</td>
<td>{{ ip[1]['last_time'] }}</td>
<td style="text-align:right">{{ ip[1]['count'] }}</td>
<td>{% if ip[1]['ports'] %}{{ ', '.join(ip[1]['ports']) }}{% else %}---{% endif %}</td>
{%- if current_user.is_authenticated %}
<td><button id="{{ 'feedback-show-' + id }}" class="btn btn-default btn-xs feedback-show-btn" data-toggle="tooltip" title="{{ gettext('Show feedback form') }}">{{ gettext('Feedback') }}</button></td>
{%- endif %}
</tr>
{%- if current_user.is_authenticated %}
<tr id="{{ 'feedback-line-' + id }}" style="display:none">
<td colspan=5>
<textarea id="{{ 'feedback-text-' + id }}" class="form-control" placeholder="{{ gettext('Please, write message for administators here') }}"></textarea>
<span id="{{ 'feedback-status-' + id }}" style="display:none"></span>
</td>
<td>
<button id="{{ 'feedback-submit-' + id }}" class="btn btn-default btn-xs feedback-submit-btn" data-toggle="tooltip" title="{{ gettext('Send feedback') }}">{{ gettext('Send') }}</button>
</td>
</tr>
{%- endif %}
{%- endfor %}
</tbody>
</table>
......@@ -399,5 +417,54 @@
});
});
$('.feedback-show-btn').click(function(event) {
var id = event.target.id.slice('feedback-show-'.length).replace(/[.\/:]/g, "\\$&");
$('#feedback-line-' + id).toggle();
$('#feedback-text-' + id).focus();
$('#feedback-show-' + id).tooltip('destroy');
event.preventDefault();
});
$('.feedback-submit-btn').click(function(event) {
var id = event.target.id.slice('feedback-submit-'.length).replace(/[.\/:]/g, "\\$&");
var feedback = $('#feedback-text-' + id)[0].value;
if (!feedback) {
$('#feedback-status-' + id)[0].style.color = "red";
$('#feedback-status-' + id)[0].innerHTML = "{{ gettext('Please, write message before sending.') }}";
$('#feedback-status-' + id).show();
$('#feedback-text-' + id).focus();
return;
}
var ip = $('#ip-' + id)[0].textContent;
var section = id.slice(0, -(ip.replace(/[.\/:]/g, "\\$&").length + 1));
$.ajax({
global: false,
type: "POST",
url: "{{ url_for('reports.feedback', item_id = item.label) }}",
data: JSON.stringify({
ip: ip,
section: section,
feedback: feedback
}),
contentType: 'application/json',
error: function (xhr, status, exc) {
console.log("Feedback ended with error: '" + status + "', '" + exc + "'");
$('#feedback-status-' + id)[0].style.color = "red";
$('#feedback-status-' + id)[0].innerHTML = "{{ gettext('We are sorry, something went wrong. Please try again later.') }}";
$('#feedback-status-' + id).show();
},
success: function (data, status, xhr) {
$('#feedback-text-' + id).prop('disabled', true);
$('#feedback-submit-' + id).prop('disabled', true);
$('#feedback-submit-' + id).tooltip('destroy');
console.log("Feedback ended with status: '" + status + "', '" + data + "'");
$('#feedback-status-' + id)[0].style.color = "green";
$('#feedback-status-' + id)[0].innerHTML = "{{ gettext('Thank you. Your feedback was sent to administrators.') }}";
$('#feedback-status-' + id).show();
}
});
event.preventDefault();
});
</script>
{%- endblock bodyjs %}
......@@ -211,6 +211,9 @@ class Config: # pylint: disable=locally-disabled,too-few-public-methods
HAWAT_ADMINS = ['root@{}'.format(socket.getfqdn())]
"""List of system administrator emails."""
HAWAT_REPORT_FEEDBACK_MAILS = ['root@{}'.format(socket.getfqdn())]
"""List of system administrator emails, that receive feedback messages for reports."""
HAWAT_LOG_DEFAULT_LEVEL = 'info'
"""Default logging level, case insensitive. One of the values ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``, ``CRITICAL``."""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment