From: Michael Tremer Date: Tue, 27 Oct 2015 00:12:29 +0000 (+0100) Subject: Add graphs handlers X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=03f518c7ff3290975e945b7c720df84cb7464558;p=people%2Fms%2Fwestferry.git Add graphs handlers This patch adds more handlers that show graphs from collecty and also extends the interface to collecty. Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index c15e1ca..292b076 100644 --- a/Makefile.am +++ b/Makefile.am @@ -117,6 +117,7 @@ westferry_handlersdir = $(pythondir)/westferry/handlers westferry_ui_PYTHON = \ src/westferry/ui/__init__.py \ src/westferry/ui/base.py \ + src/westferry/ui/graphs.py \ src/westferry/ui/menu.py \ src/westferry/ui/utils.py @@ -131,6 +132,14 @@ dist_templates_DATA = \ templates_modulesdir = $(templatesdir)/modules +dist_templates_modules_DATA = + +templates_modules_graphsdir = $(templates_modulesdir)/graphs + +dist_templates_modules_graphs_DATA = \ + src/templates/modules/graphs/box.html \ + src/templates/modules/graphs/preview.html + templates_modules_menudir = $(templates_modulesdir)/menu dist_templates_modules_menu_DATA = \ diff --git a/src/templates/graph.html b/src/templates/graph.html new file mode 100644 index 0000000..36ed148 --- /dev/null +++ b/src/templates/graph.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block main %} + {% module GraphBox(graph) %} +{% end block %} diff --git a/src/templates/graphs.html b/src/templates/graphs.html new file mode 100644 index 0000000..6c7fe15 --- /dev/null +++ b/src/templates/graphs.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block main %} + {% for g in graphs %} + {% module GraphBoxPreview(g) %} + {% end %} +{% end block %} diff --git a/src/templates/modules/graphs/box.html b/src/templates/modules/graphs/box.html new file mode 100644 index 0000000..9f9b974 --- /dev/null +++ b/src/templates/modules/graphs/box.html @@ -0,0 +1,32 @@ + + +{{ graph.title }} + + + + + {{ _("Download") }} + diff --git a/src/templates/modules/graphs/preview.html b/src/templates/modules/graphs/preview.html new file mode 100644 index 0000000..21b957e --- /dev/null +++ b/src/templates/modules/graphs/preview.html @@ -0,0 +1,10 @@ + + + + {{ graph.title }} + diff --git a/src/westferry/backend/graphs.py b/src/westferry/backend/graphs.py index 1e24aee..7a88381 100644 --- a/src/westferry/backend/graphs.py +++ b/src/westferry/backend/graphs.py @@ -35,9 +35,8 @@ class GraphsBackend(base.BaseBackend): return self._collecty - def generate_graph(self, template_name, **kwargs): - assert self.template_exists(template_name) - + @staticmethod + def _remove_defaults(kwargs): # Remove all keys with value None # dbus cannot marshall None and when None is set, we assume # that the default is selected, so we can simply remove the @@ -46,8 +45,20 @@ class GraphsBackend(base.BaseBackend): if kwargs[key] is None: del kwargs[key] + return kwargs + + def generate_graph(self, template_name, **kwargs): + assert self.template_exists(template_name) + kwargs = self._remove_defaults(kwargs) + return self.collecty.generate_graph(template_name, **kwargs) + def graph_info(self, template_name, **kwargs): + assert self.template_exists(template_name) + kwargs = self._remove_defaults(kwargs) + + return self.collecty.graph_info(template_name, **kwargs) + def template_exists(self, template_name): """ Returns True if a template with the given name exists diff --git a/src/westferry/handlers/analytics.py b/src/westferry/handlers/analytics.py index 94f291e..6779c7e 100644 --- a/src/westferry/handlers/analytics.py +++ b/src/westferry/handlers/analytics.py @@ -45,6 +45,9 @@ class AnalyticsBaseHandler(base.BaseHandler): return m + def render_graphs(self, graphs): + self.render("graphs.html", graphs=graphs) + class AnalyticsOverviewHandler(AnalyticsBaseHandler): url = r"/analytics" @@ -69,9 +72,14 @@ class AnalyticsSystemBaseHandler(AnalyticsBaseHandler): m.add_handler(AnalyticsSystemOverviewHandler, title=_("Overview")) m.add_divider() - # Others + # Most interesting items m.add_handler(AnalyticsSystemProcessorsHandler) m.add_handler(AnalyticsSystemMemoryHandler) + m.add_handler(AnalyticsSystemTemperaturesHandler) + + # Others + s = m.add_submenu(_("More")) + s.add_handler(AnalyticsSystemEntropyHandler) return m @@ -80,16 +88,69 @@ class AnalyticsSystemOverviewHandler(AnalyticsSystemBaseHandler): url = r"/analytics/system" title = N_("System") + def get(self): + self.render("base.html") + class AnalyticsSystemProcessorsHandler(AnalyticsSystemBaseHandler): url = r"/analytics/system/processors" title = N_("Processors") + def get(self): + _ = self.locale.translate + + graphs = [ + ui.graphs.Graph(self, "processor"), + ui.graphs.Graph(self, "processor-temperature"), + ui.graphs.Graph(self, "cpufreq"), + ui.graphs.Graph(self, "loadavg"), + ] + + self.render_graphs(graphs) + class AnalyticsSystemMemoryHandler(AnalyticsSystemBaseHandler): url = r"/analytics/system/memory" title = N_("Memory") + def get(self): + _ = self.locale.translate + + graphs = [ + ui.graphs.Graph(self, "memory"), + ] + + self.render_graphs(graphs) + + +class AnalyticsSystemTemperaturesHandler(AnalyticsSystemBaseHandler): + url = r"/analytics/system/temperatures" + title = N_("Temperatures") + + def get(self): + _ = self.locale.translate + + graphs = [ + ui.graphs.Graph(self, "sensors-temperature"), + ui.graphs.Graph(self, "processor-temperature"), + ] + + self.render_graphs(graphs) + + +class AnalyticsSystemEntropyHandler(AnalyticsSystemBaseHandler): + url = r"/analytics/system/entropy" + title = N_("Entropy") + + def get(self): + _ = self.locale.translate + + graphs = [ + ui.graphs.Graph(self, "entropy"), + ] + + self.render_graphs(graphs) + class GraphExportHandler(base.BaseHandler): VALID_INTERVALS = ("hour", "day", "month", "week", "year") @@ -97,7 +158,7 @@ class GraphExportHandler(base.BaseHandler): SUPPORTED_FORMATS = ("pdf", "png", "svg") - url = r"/graph/([\w\-]+)(?:/([\w\d]+))?.(%s)" % "|".join(SUPPORTED_FORMATS) + url = r"/graph/([\w\-]+)(?:/([\w\d\.]+))?\.(%s)" % "|".join(SUPPORTED_FORMATS) def get(self, template_name, object_id, format): # Get the requested dimensions of the image @@ -109,15 +170,18 @@ class GraphExportHandler(base.BaseHandler): if interval and not interval in self.VALID_INTERVALS: raise tornado.web.HTTPError(400, _("Invalid interval: %s") % interval) + # Create the graph object + g = ui.graphs.Graph(self, template_name, object_id=object_id) + # Generate the graph image - g = self.backend.graphs.generate_graph(template_name, object_id=object_id, - format=format.upper(), height=height, width=width, interval=interval) + image = g.generate_graph(format=format.upper(), interval=interval, + height=height, width=width) # Set the HTTP headers self._make_headers(format, template_name, object_id) # Deliver the content - self.finish(g) + self.finish(image.get("image")) def _make_headers(self, extension, template_name, object_id): # Determine the mime type @@ -135,3 +199,12 @@ class GraphExportHandler(base.BaseHandler): self.set_header("Content-Disposition", "attachment; filename=%s" % filename) else: self.set_header("Content-Disposition", "inline; filename=%s" % filename) + + +class GraphHandler(base.BaseHandler): + url = r"/graph/([\w\-]+)(?:/([\w\d\.]+))?" + + def get(self, template, object_id): + graph = ui.graphs.Graph(self, template, object_id=object_id) + + self.render("graph.html", graph=graph) diff --git a/src/westferry/i18n.py b/src/westferry/i18n.py new file mode 100644 index 0000000..bf71fa1 --- /dev/null +++ b/src/westferry/i18n.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +############################################################################### +# # +# Westferry - The IPFire web user interface # +# Copyright (C) 2015 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +N_ = lambda x: x diff --git a/src/westferry/ui/__init__.py b/src/westferry/ui/__init__.py index 40b0745..89ade9a 100644 --- a/src/westferry/ui/__init__.py +++ b/src/westferry/ui/__init__.py @@ -20,6 +20,7 @@ ############################################################################### from . import base +from . import graphs from . import menu from . import utils diff --git a/src/westferry/ui/graphs.py b/src/westferry/ui/graphs.py new file mode 100644 index 0000000..1098c74 --- /dev/null +++ b/src/westferry/ui/graphs.py @@ -0,0 +1,141 @@ +#!/usr/bin/python3 +############################################################################### +# # +# Westferry - The IPFire web user interface # +# Copyright (C) 2015 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +import urllib.parse + +from . import base + +DEFAULT_INTERVAL = "day" +DEFAULT_FORMAT = "svg" + + +class Graph(object): + def __init__(self, handler, template, object_id=None, title=None): + self.handler = handler + self._title = title + + # Graph identifier + self.template = template + self.object_id = object_id + + # Set the default interval + self.interval = handler.get_argument("interval", DEFAULT_INTERVAL) + self.format = handler.get_argument("format", DEFAULT_FORMAT) + + @property + def backend(self): + """ + Shortcut to the backend. + """ + return self.handler.backend + + @property + def locale(self): + """ + Shortcut to the locale + """ + return self.handler.locale + + @property + def graph_info(self): + if not hasattr(self, "_graph_info"): + self._graph_info = self.backend.graphs.graph_info(self.template, + object_id=self.object_id) + + return self._graph_info + + @property + def title(self): + return self._title or self.graph_info.get("title") + + # Format + + def get_format(self): + return self._format + + def set_format(self, format): + # TODO check for valid inputs + self._format = format + + # Interval + + def get_interval(self): + return self._interval + + def set_interval(self, interval): + # TODO check for valid inputs + self._interval = interval + + interval = property(get_interval, set_interval) + + def make_namespace(self, **kwargs): + ns = { + "format" : self.format, + "interval" : self.interval, + } + ns.update(kwargs) + + return ns + + def _make_url(self, url, format=None, **kwargs): + ns = self.make_namespace(**kwargs) + + url = url % { + "format" : format or self.format or DEFAULT_FORMAT, + "object_id" : self.object_id, + "template" : self.template, + } + + query_string = urllib.parse.urlencode(ns) + if query_string: + url += "?%s" % query_string + + return url + + def make_image_url(self, **kwargs): + if self.object_id: + url = "/graph/%(template)s/%(object_id)s.%(format)s" + else: + url = "/graph/%(template)s.%(format)s" + + return self._make_url(url, **kwargs) + + def make_url(self, **kwargs): + if self.object_id: + url = "/graph/%(template)s/%(object_id)s" + else: + url = "/graph/%(template)s" + + return self._make_url(url, **kwargs) + + def generate_graph(self, **kwargs): + return self.backend.graphs.generate_graph(self.template, + object_id=self.object_id, **kwargs) + + +class GraphBoxModule(base.BaseUIModule): + def render(self, graph): + return self.render_string("modules/graphs/box.html", graph=graph) + + +class GraphBoxPreviewModule(base.BaseUIModule): + def render(self, graph): + return self.render_string("modules/graphs/preview.html", graph=graph) diff --git a/src/westferry/ui/menu.py b/src/westferry/ui/menu.py index 5afa24b..b720ae8 100644 --- a/src/westferry/ui/menu.py +++ b/src/westferry/ui/menu.py @@ -163,6 +163,13 @@ class SubMenu(MenuItem, MenuMixin): """ return self.menu.locale + @property + def handler(self): + """ + Shortcut to the handler of the main menu + """ + return self.menu.handler + class SidebarMenuModule(base.BaseUIModule): def render(self, menu):