It is now possible to provide additional graphic sets.

see #333
This commit is contained in:
Antoine Nguyen
2012-11-12 23:12:39 +01:00
parent c304ac1b6c
commit c65ce99193
7 changed files with 139 additions and 74 deletions
+9
View File
@@ -21,6 +21,8 @@ class Stats(ModoExtension):
needs_media = True
def load(self):
events.registerEvent("GetGraphSets")
parameters.register_admin("LOGFILE", type="string",
deflt="/var/log/mail.log",
help=ugettext_lazy("Path to log file used to collect statistics"))
@@ -45,3 +47,10 @@ def menu(target, user):
"label" : _("Statistics"),
"url" : reverse('fullindex')}
]
@events.observe("GetGraphSets")
def get_default_graph_sets():
from graph_templates import MailTraffic
gset = MailTraffic()
return {gset.html_id: gset}
+67 -44
View File
@@ -2,50 +2,73 @@
from django.utils.translation import ugettext_lazy
class MailTrafic(object):
pass
class Graph(object):
width = 540
height = 120
cf = 'AVERAGE'
traffic_avg_template = {
'name' : 'traffic',
'title' : ugettext_lazy("Average normal traffic"),
'width' : '540',
'height' : '120',
'vertlabel' : ugettext_lazy('msgs/min'),
'cf' : 'AVERAGE',
'vars' : {
'sent' : { "type" : "AREA", "color" : "#00FF00",
"legend" : ugettext_lazy("sent messages") },
'recv' : { "type" : "AREA", "color" : "#0000FF",
"legend" : ugettext_lazy("received messages") },
}
}
def __init__(self, **ds):
self.vars = {}
for dsname, dsdef in ds.iteritems():
self.vars[dsname] = dsdef
badtraffic_avg_template = {
'name' : 'badtraffic',
'title' : ugettext_lazy("Average bad traffic"),
'width' : '540',
'height' : '120',
'vertlabel' : ugettext_lazy('msgs/min'),
'cf' : 'AVERAGE',
'vars' : {
'bounced' : { "type" : "AREA", "color" : "#FFFF00",
"legend" : ugettext_lazy("bounced messages") },
'reject' : { "type" : "AREA", "color" : "#FF0000",
"legend" : ugettext_lazy("rejected messages") },
}
}
@property
def display_name(self):
return self.__class__.__name__.lower()
size_avg_template = {
'name' : 'size',
'title' : ugettext_lazy("Average normal traffic size"),
'width' : '540',
'height' : '120',
'vertlabel' : ugettext_lazy('bytes/min'),
'cf' : 'AVERAGE',
'vars' : {
'size_recv' : { "type" : "AREA", "color" : "#FF9900",
"legend" : ugettext_lazy("received size") },
'size_sent' : { "type" : "AREA", "color" : "#339999",
"legend" : ugettext_lazy("sent size") },
}
}
class AvgTraffic(Graph):
title = ugettext_lazy('Average normal traffic')
vertlabel = ugettext_lazy('msgs/min')
def __init__(self):
super(AvgTraffic, self).__init__(
sent={'type' : 'AREA', 'color' : '#00FF00',
"legend" : ugettext_lazy("sent messages")},
recv={"type" : "AREA", "color" : "#0000FF",
"legend" : ugettext_lazy("received messages")}
)
class AvgBadTraffic(Graph):
title = ugettext_lazy('Average bad traffic')
vertlabel = ugettext_lazy('msgs/min')
def __init__(self):
super(AvgBadTraffic, self).__init__(
bounced={'type' : 'AREA', 'color' : '#FFFF00',
"legend" : ugettext_lazy("bounced messages")},
reject={"type" : "AREA", "color" : "#FF0000",
"legend" : ugettext_lazy("rejected messages")}
)
class AvgTrafficSize(Graph):
title = ugettext_lazy('Average normal traffic size')
vertlabel = ugettext_lazy('bytes/min')
def __init__(self):
super(AvgTrafficSize, self).__init__(
size_recv={'type' : 'AREA', 'color' : '#FF9900',
"legend" : ugettext_lazy("received size")},
size_sent={"type" : "AREA", "color" : "#339999",
"legend" : ugettext_lazy("sent size")}
)
class GraphSet(object):
title = None
graphs = []
@property
def html_id(self):
return self.__class__.__name__.lower()
def get_graphs(self):
if not hasattr(self, '__graph_instances'):
self.__graph_instances = map(lambda gc: gc(), self.graphs)
return self.__graph_instances
def get_graph_names(self):
return map(lambda g: g.display_name, self.get_graphs())
class MailTraffic(GraphSet):
title = ugettext_lazy('Mail traffic')
graphs = [AvgTraffic, AvgBadTraffic, AvgTrafficSize]
@@ -28,8 +28,8 @@ from optparse import make_option
from modoboa.lib import parameters
from modoboa.admin.models import Domain
from modoboa.extensions.stats import Stats
from modoboa.extensions.stats.grapher import Grapher
from modoboa.extensions.stats.graph_templates import *
from modoboa.extensions.stats.grapher import *
from modoboa.extensions.stats.graph_templates import MailTraffic
rrdstep = 60
xpoints = 540
@@ -213,7 +213,7 @@ class LogParser(object):
se = int(int(se) / rrdstep) # rrd step is one-minute => se = 0
if prev_se != se or prev_mi != mi or prev_ho != ho:
cur_t = grapher.str2Time(self.year(mo), mo, da, ho, mi, se)
cur_t = str2Time(self.year(mo), mo, da, ho, mi, se)
cur_t = cur_t - cur_t % rrdstep
prev_mi = mi
prev_ho = ho
@@ -274,10 +274,8 @@ class LogParser(object):
for t in sorted(data.keys()):
self.update_rrd(dom, t)
G.make_defaults(dom, tpl=traffic_avg_template)
G.make_defaults(dom, tpl=badtraffic_avg_template)
G.make_defaults(dom, tpl=size_avg_template)
for graph_tpl in MailTraffic().get_graphs():
G.make_defaults(dom, graph_tpl)
class Command(BaseCommand):
help = 'Log file parser'
@@ -69,7 +69,7 @@ Stats.prototype = {
$("body").notify("error", data.respmsg);
return;
}
$("#graphs_mailstats").html(data.content);
$(".tab-pane.active").html(data.content);
},
search_domain: function(e) {
@@ -8,7 +8,7 @@
$(document).ready(function() {
var stats = new Stats({
graphurl: "{% url modoboa.extensions.stats.views.graphs %}",
deflocation: "{{ deflocation }}"
deflocation: "{{ deflocation|safe }}"
});
});
</script>
@@ -44,12 +44,14 @@ $(document).ready(function() {
{% endblock %}
{% block apparea %}
<div id="graphs" class="tabbable">
<ul class="nav nav-tabs">
<li class="active"><a href="#graphs_mailstats" data-toggle="tab">{% trans "Mail trafic" %}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="graphs_mailstats"></div>
</div>
<ul class="nav nav-tabs">{% for id, gset in graph_sets.items %}
<li {% if forloop.first %}class="active"{% endif %}>
<a href="#graphs_{{ gset.html_id }}" data-toggle="tab">{{ gset.title }}</a>
</li>
</ul>{% endfor %}
<div class="tab-content">{% for id, gset in graph_sets.items %}
<div class="tab-pane {% if forloop.first %}active{% endif %}" id="graphs_{{ gset.html_id }}"></div>
</div>{% endfor %}
</div>
{% endblock %}
+19 -13
View File
@@ -6,6 +6,7 @@ from django.contrib.auth.decorators \
import login_required, user_passes_test, permission_required
from modoboa.extensions.stats.grapher import *
from modoboa.lib.exceptions import *
from modoboa.lib import events
from graph_templates import *
periods = [{"name" : "day", "label" : ugettext_lazy("Day")},
@@ -16,27 +17,36 @@ periods = [{"name" : "day", "label" : ugettext_lazy("Day")},
@login_required
@permission_required("admin.view_mailboxes")
def index(request):
deflocation = ""
"""
FIXME: how to select a default graph set ?
"""
deflocation = "graphs/?gset=mailtraffic&"
if request.user.is_superuser:
deflocation = "graphs/?view=global"
deflocation += "view=global"
else:
if len(request.user.get_domains()):
deflocation = "graphs/?view=%s" % request.user.get_domains()[0].name
deflocation += "view=%s" % request.user.get_domains()[0].name
else:
raise ModoboaException(_("No statistics available"))
period = request.GET.get("period", "day")
graph_sets = events.raiseDictEvent('GetGraphSets')
return _render(request, 'stats/index.html', {
"periods" : periods,
"period" : period,
"selection" : "stats",
"deflocation" : deflocation
"deflocation" : deflocation,
"graph_sets" : graph_sets
})
@login_required
@user_passes_test(lambda u: u.group != "SimpleUsers")
def graphs(request):
view = request.GET.get("view", None)
gset = request.GET.get("gset", None)
gsets = events.raiseDictEvent("GetGraphSets")
if not gset in gsets:
raise ModoboaException(_("Unknown graphic sets"))
if not view:
raise ModoboaException(_("Invalid request"))
period = request.GET.get("period", "day")
@@ -61,19 +71,15 @@ def graphs(request):
end = request.GET["end"]
G = Grapher()
period_name = "%s_%s" % (start.replace('-',''), end.replace('-',''))
for tpl in [traffic_avg_template, badtraffic_avg_template, size_avg_template]:
tplvars['graphs'].append(tpl['name'])
G.process(tplvars["domain"],
period_name,
str2Time(*start.split('-')),
str2Time(*end.split('-')),
tpl)
for tpl in gsets[gset].get_graphs():
tplvars['graphs'].append(tpl.display_name)
G.process(tplvars["domain"], period_name, str2Time(*start.split('-')),
str2Time(*end.split('-')), tpl)
tplvars["period_name"] = period_name
tplvars["start"] = start
tplvars["end"] = end
else:
# FIXME
tplvars['graphs'] = ['traffic', 'badtraffic', 'size']
tplvars['graphs'] = gsets[gset].get_graph_names()
return ajax_simple_response(dict(
status="ok", content=_render_to_string(request, "stats/graphs.html", tplvars)
+29 -2
View File
@@ -63,6 +63,14 @@ events = [
callbacks = {}
def registerEvent(name):
"""Register a new event
:param name: the event's name
"""
if not name in events:
events.append(name)
def register(event, callback):
"""Register a plugin callback for a specific event
@@ -71,8 +79,9 @@ def register(event, callback):
:param event: the event to listen to
:param callback: a function to execute when the event is raised
"""
if not event in events:
return 0
# if not event in events:
# print "Event %s not registered" % event
# return 0
if not event in callbacks.keys():
callbacks[event] = {}
fullname = "%s.%s" % (callback.__module__, callback.__name__)
@@ -172,3 +181,21 @@ def raiseQueryEvent(event, *args):
for name, callback in callbacks[event].iteritems():
result += callback(*args)
return result
def raiseDictEvent(event, *args):
"""Raise a specific event and return result as a dictionnary
Any additional keyword argument will be passed to registered
callbacks. Callback answers must be dictionnaries.
:param event: the event to raise
:return: a dictionnary
"""
result = {}
if not event in events or not event in callbacks.keys():
return result
for name, callback in callbacks[event].iteritems():
tmp = callback(*args)
for k, v in tmp.iteritems():
result[k] = v
return result