]> git.ipfire.org Git - people/ms/westferry.git/commitdiff
Add graphs handlers
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 27 Oct 2015 00:12:29 +0000 (01:12 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 27 Oct 2015 00:12:29 +0000 (01:12 +0100)
This patch adds more handlers that show graphs from collecty and
also extends the interface to collecty.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/templates/graph.html [new file with mode: 0644]
src/templates/graphs.html [new file with mode: 0644]
src/templates/modules/graphs/box.html [new file with mode: 0644]
src/templates/modules/graphs/preview.html [new file with mode: 0644]
src/westferry/backend/graphs.py
src/westferry/handlers/analytics.py
src/westferry/i18n.py [new file with mode: 0644]
src/westferry/ui/__init__.py
src/westferry/ui/graphs.py [new file with mode: 0644]
src/westferry/ui/menu.py

index c15e1ca222142369f472b860123a81acd91b61ef..292b076e81545c43da0dcccee3be6490d8586e13 100644 (file)
@@ -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 (file)
index 0000000..36ed148
--- /dev/null
@@ -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 (file)
index 0000000..6c7fe15
--- /dev/null
@@ -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 (file)
index 0000000..9f9b974
--- /dev/null
@@ -0,0 +1,32 @@
+<div class="page-header">
+       <h3>{{ graph.title }}</h3>
+</div>
+
+<img class="img-responsive img-thumbnail" src="{{ graph.make_image_url(width="1130") }}"
+       alt="{{ graph.title }}">
+
+<ul class="nav nav-pills">
+       {% for interval in ("hour", "day", "week", "month", "year") %}
+               <li {% if graph.interval == interval %}class="active"{% end %}>
+                       <a href="{{ graph.make_url(interval=interval) }}">
+                               {% if interval == "hour" %}
+                                       {{ _("Hour") }}
+                               {% elif interval == "day" %}
+                                       {{ _("Day") }}
+                               {% elif interval == "week" %}
+                                       {{ _("Week") }}
+                               {% elif interval == "month" %}
+                                       {{ _("Month") }}
+                               {% elif interval == "year" %}
+                                       {{ _("Year") }}
+                               {% else %}
+                                       {{ interval }}
+                               {% end %}
+                       </a>
+               </li>
+       {% end %}
+</ul>
+
+<a class="btn btn-default btn-sm" href="{{ graph.make_image_url(format="pdf") }}">
+       {{ _("Download") }}
+</a>
diff --git a/src/templates/modules/graphs/preview.html b/src/templates/modules/graphs/preview.html
new file mode 100644 (file)
index 0000000..21b957e
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="page-header">
+       <h3>
+               <a href="{{ graph.make_url() }}">{{ graph.title }}</a>
+       </h3>
+</div>
+
+<a href="{{ graph.make_url(interval="week") }}">
+       <img class="img-responsive img-thumbnail" src="{{ graph.make_image_url() }}"
+               alt="{{ graph.title }}">
+</a>
index 1e24aeec60a72f9ca164ee1e5b931b2cd4733ffa..7a88381b150d46ebdb510d4d58717259e46331d5 100644 (file)
@@ -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
index 94f291ed1b58d849d761269848577677cd066d3b..6779c7e14128d2cde43faed79ea67fca54ae23de 100644 (file)
@@ -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 (file)
index 0000000..bf71fa1
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+N_ = lambda x: x
index 40b0745e137a284a3c1190a78144fa4ce3fcf55e..89ade9a1360d0b404f4162917ba9fd0544316ec6 100644 (file)
@@ -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 (file)
index 0000000..1098c74
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+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)
index 5afa24b062ba59e01278a8af07fa7b4ab4247d76..b720ae8d4541b6f494a9a3d125cd0c0d8a2ce992 100644 (file)
@@ -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):