pvl/verkko/rrd.py
author Tero Marttila <terom@paivola.fi>
Sun, 22 Dec 2013 19:03:57 +0200
changeset 336 edaa5d0aa57d
parent 201 b97929bcef05
permissions -rw-r--r--
version 0.6.1: pvl.hosts forward/reverse delegation, and include= support
# encoding: utf-8
"""
    http://verkko.paivola.fi/rrd
"""

from pvl import web
from pvl.web import urls
from pvl.web.html import tags as html

import pvl.web.response
import pvl.rrd

import logging; log = logging.getLogger('pvl.verkko.rrd')

# errors
import os.path

class RRDNotFound (pvl.web.response.NotFound) :
    """
        404 Not Found for tree/rrd.
    """

    def __init__ (self, tree, rrd=None) :
        if tree and rrd :
            path = os.path.join(tree, rrd)
        elif tree :
            path = tree
        elif rrd :
            path = rrd
        else :
            path = ''

        pvl.web.response.NotFound.__init__(self, path)

# View/Controller
class Handler (web.Handler) :
    CSS = (
        "/static/rrd/rrd.css", 
    )

    def title (self) :
        return u"Network RRD Traffic Graphs"

    def breadcrumb (self, _tree, target=None) :
        """
            Yield (title, url) navigation breadcrumbs
        """

        yield '/', self.url(Index)
        
        if _tree :
            tree = ''
            
            for part in _tree.split('/') :
                tree = urls.join(tree, part)

                yield part, self.url(Index, tree=tree)

        if target :
            # Target
            yield target, self.url(Target, tree=tree, target=self.target)

    def render_breadcrumb (self, tree, target=None) :
        """
            Render breadcrumb -> html.div
        """

        return html.div(id='breadcrumb')(html(" &raquo; ".join(
            str(html.a(href=url)(node)) for node, url in self.breadcrumb(tree, target)))
        )

class Index (Handler) :
    """
        Browse trees, show overview graphs for targets.
    """

    def _title (self) :
        if self.tree :
            return html(" &raquo; ".join(self.tree.split('/')))
        else :
            return ""
 
    def url_tree (self, node) :
        """
            Return url for given sub-node.
        """
        
        if self.tree :
            path = urls.join(self.tree, node)
        else :
            path = node
        
        return self.url(tree=path)

    def process (self, tree=None) :
        """
            Lookup path -> self.tree.
        """

        if tree :
            try :
                # XXX: unicode?
                self.tree = self.app.rrd.tree(tree)

            except ValueError as ex :
                # mask
                raise RRDNotFound(tree)
        else :
            # root
            self.tree = self.app.rrd.tree()

    def render_list (self, items) :
        return (
            html.li(class_=('odd' if idx % 2 else 'even'))(item) for idx, item in enumerate(items)
        )

    def render_rrd (self, rrd) :
        """
            Render overview link/image for given rrd.
        """

        target_url = self.url(Target, tree=self.tree, target=rrd)
        graph_url = self.url(Graph, tree=self.tree, target=rrd)
        
        return html.a(href=target_url)(
                html.h3(rrd),
                html.img(src=graph_url),
        )

    def render (self) :
        """
            Render list of trees/rrds.
        """

        trees, rrds = self.app.rrd.list(self.tree)

        return self.render_breadcrumb(self.tree), html.div(id='overview')(
                html.ul(id='tree-list')(
                    self.render_list(
                        html.a(href=self.url_tree(subtree))(subtree)
                    for subtree in trees)
                ) if trees else None,

                html.hr() if trees and rrds else None,

                html.ul(id='rrd-list')(
                    self.render_list(
                        self.render_rrd(rrd)
                    for rrd in rrds)
                ) if rrds else None,
        )

class Target (Handler) :
    """
        Show graphs for RRD file.
    """
    
    def _title (self) :
        return html(" &raquo; ".join(self.rrd.split('/')))
        
    def process (self, target, tree=None) :
        """
            Lookup tree/target -> self.target
        """
        
        try :
            self.tree = self.app.rrd.tree(tree)
            self.rrd = self.app.rrd.rrd(target, self.tree)
            self.target = target

        except ValueError as ex :
            raise RRDNotFound(tree, target)

    def render_interval (self, interval, style='detail') :
        """
            Render detail link/image.
        """

        graph_url = self.url(Graph, tree=self.tree, target=self.target, style=style, interval=interval)
        
        return (
                html.h2(interval.title()),
                html.img(src=graph_url)
        )

    INTERVALS = ('daily', 'weekly', 'yearly')

    def render (self) :
        return self.render_breadcrumb(self.tree, self.target), html.div(id='detail')(
                self.render_interval(interval) for interval in self.INTERVALS
        )

from pvl.invoke import merge # XXX
import werkzeug # wrap_file

class Graph (Handler) :
    """
        Render graph for RRD.
    """

    ARGS = { 'interval': 'daily', 'style': 'overview' }

    def process (self, tree, target, style, interval) :
        """
            Return Graph for given options.
        """

        try :
            self.tree = self.app.rrd.tree(tree)
            self.rrd = self.app.rrd.rrd(target, self.tree)

        except ValueError as ex :
            raise RRDNotFound(tree, target)
        
        self.style = style
        self.interval = interval

    def render_png (self) :
        """
            Return PNG data as a file-like object for our graph.
        """

        return self.app.rrd.graph(self.rrd, self.style, self.interval)

    def respond (self) :
        """
            Return Response for our request.
        """
        
        # process params+args -> self.graph
        process = merge(self.params, dict((arg, self.request.args.get(arg, default)) for arg, default in self.ARGS.iteritems()))
        response = self.process(**process)

        if response :
            return response
        
        # PNG output
        file = self.render_png()

        # respond with file wrapper
        return pvl.web.response.image(self.response_file(file), type='png')

# WSGI
class Application (web.Application) :
    # dispatch
    URLS = urls.Map((
        urls.rule('/',                              Index),
        urls.rule('/<target>',                      Target),
        urls.rule('/<path:tree>/',                  Index),
        urls.rule('/<path:tree>/<target>',          Target),
        urls.rule('/<path:tree>/<target>.png',      Graph),
    ))

    def __init__ (self, rrd, **opts) :
        """
            rrd     - pvl.rrd.RRDDatabase
        """

        super(Application, self).__init__(**opts)

        self.rrd = rrd