diff --git a/modoboa/extensions/stats/__init__.py b/modoboa/extensions/stats/__init__.py index e58bd43c2..808bc2a40 100644 --- a/modoboa/extensions/stats/__init__.py +++ b/modoboa/extensions/stats/__init__.py @@ -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} diff --git a/modoboa/extensions/stats/graph_templates.py b/modoboa/extensions/stats/graph_templates.py index ce629c822..5350a4c0d 100644 --- a/modoboa/extensions/stats/graph_templates.py +++ b/modoboa/extensions/stats/graph_templates.py @@ -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] diff --git a/modoboa/extensions/stats/management/commands/logparser.py b/modoboa/extensions/stats/management/commands/logparser.py index 758ad3910..697710610 100644 --- a/modoboa/extensions/stats/management/commands/logparser.py +++ b/modoboa/extensions/stats/management/commands/logparser.py @@ -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' diff --git a/modoboa/extensions/stats/static/stats/js/stats.js b/modoboa/extensions/stats/static/stats/js/stats.js index d371966cf..eb51ef009 100644 --- a/modoboa/extensions/stats/static/stats/js/stats.js +++ b/modoboa/extensions/stats/static/stats/js/stats.js @@ -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) { diff --git a/modoboa/extensions/stats/templates/stats/index.html b/modoboa/extensions/stats/templates/stats/index.html index 6eb171e5e..2842aeee7 100644 --- a/modoboa/extensions/stats/templates/stats/index.html +++ b/modoboa/extensions/stats/templates/stats/index.html @@ -8,7 +8,7 @@ $(document).ready(function() { var stats = new Stats({ graphurl: "{% url modoboa.extensions.stats.views.graphs %}", - deflocation: "{{ deflocation }}" + deflocation: "{{ deflocation|safe }}" }); }); @@ -44,12 +44,14 @@ $(document).ready(function() { {% endblock %} {% block apparea %}
- -
-
-
+ {% endfor %} +
{% for id, gset in graph_sets.items %} +
+
{% endfor %}
{% endblock %} diff --git a/modoboa/extensions/stats/views.py b/modoboa/extensions/stats/views.py index bc7cadaba..ceccdc93c 100644 --- a/modoboa/extensions/stats/views.py +++ b/modoboa/extensions/stats/views.py @@ -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) diff --git a/modoboa/lib/events.py b/modoboa/lib/events.py index b69844dc2..6fb3245fa 100644 --- a/modoboa/lib/events.py +++ b/modoboa/lib/events.py @@ -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