43 Commits

Author SHA1 Message Date
Daniele Sluijters
c6c4bc1679 README: Add newline after code-block. 2013-10-14 13:05:22 +02:00
Daniele Sluijters
9be5aaebd9 0.0.2: Release.
This is the second 'release' for Puppetboard introducing some new
features and compatibility with PuppetDB 1.5 / v3 API.
2013-10-14 12:53:25 +02:00
Daniele Sluijters
a75b08f882 nodes: Check PUPPETDB_API, not _EXPERIMENTAL. 2013-10-14 12:53:25 +02:00
Daniele Sluijters
f85f0fa864 requirements: Upgrade to pypuppetdb 0.0.4 2013-10-14 12:53:25 +02:00
Daniele Sluijters
095967e445 Merge pull request #20 from nibalizer/graph_fix
_macros.html: Sorting and bucketing facts graphs.
2013-10-13 12:32:48 -07:00
Spencer Krum
8a56e83b69 puppetboard/templates/_macros.html
This adds sorting and bucketing to the two graphs in the facts
view.
2013-10-13 12:25:56 -07:00
Daniele Sluijters
934f90c12a _macros.html: Graphs a bit smaller, FlatUI colors.
Currently the graphs are shown at the top of the page and because of
that really take up a lot of screen real estate. This commit makes the
graphs a bit smaller but we really need to rethink the layout here.

Additionally we switch the colors to the FlatUI swatches to match the
rest of the theme better.
2013-10-13 15:31:33 +02:00
Spencer Krum
0422b2ccd0 _macros.html: mod ensure different adjecent colors 2013-10-13 15:01:04 +02:00
Spencer Krum
e1603608bc _macros.html: Add a second Pie chart to facts/<fact>
The second chart is in order of the value of the fact. So in the
uptime fact we see 1 day before 2 days before 50 days.
2013-10-13 15:00:03 +02:00
Spencer Krum
23af033cbb facts: Add graph for facts endpoint. 2013-10-13 14:59:22 +02:00
Spencer Krum
e71f30ab50 Add flag to enable or disable the query page
Puppetboard is an excelent radiator of information, but sometimes
we want to expose information to users we don't trust giving full
access to the PuppetDB query language.

I would reccomend that Puppetboard be run twice. One, with query
enabled, run on a port behind apache login. Another, with query
disabled, run unproxied for the unwashed masses.

Closes #10.
2013-10-13 14:32:24 +02:00
Daniele Sluijters
cc87e54cea Support PuppetDB API v3.
Recently changes were made to pypuppetdb to support, at a basic level,
the v3 API introduced with PuppetDB 1.5.

This commit changes some internals of Puppetboard to handle the new
situation:
 * The experimental endpoints are gone, so is the ExperimentalDisabled
   Error;
 * v2 API can no longer access Reports/Events so we now check that we're
   talking at least v3 API;
 * Introduce a configuration setting PUPPETDB_API which takes an integer
   repersenting the API version we want to talk.
2013-10-11 11:20:34 +02:00
Daniele Sluijters
3b4db8f37e Merge pull request #19 from daniellawrence/fact_table_template_title_and_links
Add links for facts and nodes in fact table macro
2013-10-11 00:55:31 -07:00
Daniel Lawrence
3fd8a0aad4 Add links for facts and nodes in fact table macro
Added links to the node page from the facts_table if show_nodes is True.
Added links to the facts page from the facts_table if show_nodes is
False.
Added logic to switch TH table from facts to nodes if show_nodes is True.
2013-10-11 14:46:39 +11:00
Daniele Sluijters
9b662a661e README: Fix the issue link 2013-10-08 14:30:24 +02:00
Daniele Sluijters
cfb1383025 README: Add a Getting Help section
[noci]
2013-10-08 14:29:41 +02:00
Daniele Sluijters
8db60d14bd nodes: Only show report button if we have a report
Based on if the report_timestamp is truthy we'll show the reports
button, instead of always having it show up.
2013-08-22 13:17:48 -07:00
Daniele Sluijters
8cf181a9e8 Add screenshots of the new metrics tab. 2013-08-22 07:37:18 -07:00
Daniele Sluijters
543f706fd7 Merge pull request #7 from nicklewis/add-metric-name
Add the name of the metric to the metric page
2013-08-21 17:08:45 -07:00
Nick Lewis
644e169a7f Add the name of the metric to the metric page 2013-08-21 17:07:24 -07:00
Daniele Sluijters
b25d85bd32 dev: Import DEV_LISTEN_HOST 2013-08-21 16:57:12 -07:00
Daniele Sluijters
9918ec8f4b Merge pull request #6 from nicklewis/sort-metrics
Sort list of metrics and metrics data
2013-08-21 16:55:38 -07:00
Daniele Sluijters
da68bb259b Merge pull request #4 from nibalizer/dev_expose_config
dev.py: exposing some parameters
2013-08-21 16:53:01 -07:00
Nick Lewis
3c05071aef Sort list of metrics and metrics data
This makes the metrics a lot easier to scan, because it groups similar
metrics (like all the HTTP metrics) as well as the percentiles, etc.
2013-08-21 16:52:52 -07:00
Spencer Krum
cb64b73832 dev.py: exposing some parameters 2013-08-21 16:37:25 -07:00
Daniele Sluijters
c3821e777f Add a Metrics tab.
This gives you access to all metrics.
2013-08-21 16:21:02 -07:00
Daniele Sluijters
c04d45f602 README: add a TOC 2013-08-21 14:59:09 -07:00
Daniele Sluijters
d900ccf09a overview: Fix string formatting for py26. 2013-08-15 00:55:58 +02:00
Daniele Sluijters
8b3f3ea61e Merge pull request #3 from nibalizer/readme_forge
README.rst: adding forge command to readme
2013-08-14 15:53:42 -07:00
Spencer Krum
f273d24f80 README.rst: adding forge command to readme 2013-08-14 11:04:53 -07:00
Daniele Sluijters
d152d8e3a1 README: Got Spencer's first and last name mixed up 2013-08-12 20:12:56 +02:00
Daniele Sluijters
c64a2b79b2 README: Forgot the newlines for bullets. 2013-08-12 19:26:23 +02:00
Daniele Sluijters
083da989de README: Add a third party section. 2013-08-12 19:25:13 +02:00
Daniele Sluijters
c3a9b5e81c Merge pull request #2 from nibalizer/typo_fix
templates/query.html Fix typo in placeholder text
2013-08-12 01:32:07 -07:00
Spencer Krum
00d0f96914 templates/query.html Fix typo in placeholder text
The placeholder text on the query form wasn't correct syntax.
2013-08-11 17:10:47 -07:00
Daniele Sluijters
7b71eb39d2 requirements. Update to pypuppetdb 0.0.2.
There was a stupid packaging error in pypuppetdb 0.0.1 preventing
successful installation of it and its depedencies.
2013-08-09 16:54:45 +02:00
Daniele Sluijters
1a178ef2af requirements: Get rid of wsgiref.
http://stackoverflow.com/questions/6627035
2013-08-09 09:35:37 +02:00
Daniele Sluijters
4d80b6c128 template/query: Fix a typo in experimental.
Thanks @geekygirldawn!
2013-08-08 20:57:40 +02:00
Daniele Sluijters
462fcbf76c requirements: Add missing dependencies.
No idea what happend but requirements.txt should list all the
dependencies, not just the 'top' ones.
2013-08-08 18:17:48 +02:00
Daniele Sluijters
c8825d3d92 Merge pull request #1 from hunner/fix_reqs
Missing = sign
2013-08-08 09:06:59 -07:00
Hunter Haugen
1d705d04dd Missing = sign 2013-08-07 17:38:39 -07:00
Daniele Sluijters
88d1944b4b utils: Remove unused import. 2013-08-07 16:17:40 +02:00
Daniele Sluijters
bda3adc078 utils: Group the exceptions in yield_or_abort.
Additionally, the empty yield is rather unnecessary.
2013-08-07 16:15:34 +02:00
24 changed files with 2070 additions and 69 deletions

41
CHANGELOG.rst Normal file
View File

@@ -0,0 +1,41 @@
#########
Changelog
#########
0.0.2
=====
In this release we've introduced a few new things. First of all we now require
pypuppetdb version 0.0.4 or later which includes support for the v3 API
introduced with PuppetDB 1.5.
Because of changes in PuppetDB 1.5 and therefor in pypuppetdb users of the v2
API, regardless of the PuppetDB version, will no longer be able to view reports
or events.
In light of this the following settings have been removed:
* ``PUPPETDB_EXPERIMENTAL``
Two new settings have been added:
* ``PUPPETDB_API``: an integer, defaulting to ``3``, representing the API
version we want to use.
* ``ENABLE_QUERY``: a boolean, defaulting to ``True``, on wether or not to
be able to use the Query tab.
We've also added a few new features:
* Thanks to some work done during PuppetConf together with Nick Lewis (from
Puppet Labs) we now expose all of PuppetDB's metrics in the Metrics tab. The
formatting isn't exactly pretty but it's a start.
* Spencer Krum added the graphing capabilities to the Facts tab.
* Daniel Lawrence added a feature so that facts on the node view are clickable
and take you to the complete overview of that fact for your infrastructure
and made the nodes in the complete facts list clickable so you can jump to a
node.
* Klavs Klavsen contributed some documentation on how to run Puppetboard with
Passenger.
0.0.1
=====
Initial release.

View File

@@ -17,12 +17,14 @@ Because this project is powered by Flask we are restricted to:
* Python 2.6 * Python 2.6
* Python 2.7 * Python 2.7
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/node-experimental.png .. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/node-v3.png
:alt: View of a node :alt: View of a node
:width: 1024 :width: 1024
:height: 700 :height: 700
:align: center :align: center
.. contents::
Word of caution Word of caution
=============== ===============
@@ -66,6 +68,7 @@ and ``uwsgi`` can deal with.
* Apache mod_wsgi configuration: http://flask.pocoo.org/docs/deploying/mod_wsgi/ * Apache mod_wsgi configuration: http://flask.pocoo.org/docs/deploying/mod_wsgi/
* uwsgi configuration: ``uwsgi --http :9090 --wsgi-file /path/to/puppetboard/wsgi.py`` * uwsgi configuration: ``uwsgi --http :9090 --wsgi-file /path/to/puppetboard/wsgi.py``
* Passenger
In the case of uwsgi you'll of course need something like nginx in front of it to In the case of uwsgi you'll of course need something like nginx in front of it to
proxy the requests to it. proxy the requests to it.
@@ -74,6 +77,33 @@ Don't forget that you also need to serve the ``static/`` folder on the
``/static`` URL of your vhost. (I'm considering embedding the little additional ``/static`` URL of your vhost. (I'm considering embedding the little additional
Javascript and CSS this application has so no one has to bother with that). Javascript and CSS this application has so no one has to bother with that).
Passenger
^^^^^^^^^
From within the Puppetboard checkout:
.. code-block:: bash
mkdir public
mkdir tmp
ln -s wsgi.py passenger_wsgi.py
The apache vhost configuration:
.. code-block::
<VirtualHost *:80>
ServerName puppetboard.example.tld
DocumentRoot /path/to/puppetboard/public
RackAutoDetect On
Alias /static /path/to/puppetboard/static
<Directory /path/to/puppetboard/>
Options None
Order allow,deny
allow from all
</Directory>
</VirtualHost>
Configuration Configuration
============= =============
@@ -93,6 +123,42 @@ PuppetDB's experimental API endpoints.
However, if you haven't enabled them for Puppet it isn't particularily However, if you haven't enabled them for Puppet it isn't particularily
useful to enable them here as there will be no data to retrieve. useful to enable them here as there will be no data to retrieve.
Getting Help
============
This project is still very new so it's not inconceivable you'll run into
issues.
For bug reports you can file an `issue`_. If you need help with something
feel free to hit up `@daenney`_ by e-mail or find him on IRC. He can usually
be found on `IRCnet`_ and `Freenode`_ and idles in #puppet.
There's now also the #puppetboard channel on `Freenode`_ where we hang out
and answer questions related to pypuppetdb and Puppetboard.
.. _issue: https://github.com/nedap/puppetboard/issues
.. _@daenney: https://github.com/daenney
.. _IRCnet: http://www.ircnet.org
.. _Freenode: http://freenode.net
Third party
===========
Some people have already started building things with and around Puppetboard.
`Hunter Haugen`_ has provided a Vagrant setup:
* https://github.com/hunner/puppetboard-vagrant
`Spencer Krum`_ created a Puppet module to install Puppetboard with:
* https://github.com/nibalizer/puppet-module-puppetboard
You can install it with:
puppet module install nibalizer-puppetboard
.. _Hunter Haugen: https://github.com/hunner
.. _Spencer Krum: https://github.com/nibalizer
Contributing Contributing
============ ============
We welcome contributions to this project. However, there are a few ground We welcome contributions to this project. However, there are a few ground
@@ -152,22 +218,34 @@ Screenshots
:height: 700 :height: 700
:align: center :align: center
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/metrics.png
:alt: Query view
:width: 1024
:height: 700
:align: center
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/metric.png
:alt: Query view
:width: 1024
:height: 700
:align: center
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/query.png .. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/query.png
:alt: Query view :alt: Query view
:width: 1024 :width: 1024
:height: 700 :height: 700
:align: center :align: center
With experimental endpoints API v3
--------------------------- ------
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/nodes-experimental.png .. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/nodes-v3.png
:alt: Nodes table with experimental endpoints enabled :alt: Nodes table with experimental endpoints enabled
:width: 1024 :width: 1024
:height: 700 :height: 700
:align: center :align: center
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/node-experimental.png .. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/node-v3.png
:alt: Node view with experimental endpoints enabled :alt: Node view with experimental endpoints enabled
:width: 1024 :width: 1024
:height: 700 :height: 700
@@ -178,18 +256,3 @@ With experimental endpoints
:width: 1024 :width: 1024
:height: 700 :height: 700
:align: center :align: center
Error page
----------
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/no-experimental.png
:alt: Accessing disabled experimental feature
:width: 1024
:height: 700
:align: center
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/broken.png
:alt: Error message
:width: 1024
:height: 700
:align: center

3
dev.py
View File

@@ -2,7 +2,8 @@ from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
from puppetboard.app import app from puppetboard.app import app
from puppetboard.default_settings import DEV_LISTEN_HOST, DEV_LISTEN_PORT
if __name__ == '__main__': if __name__ == '__main__':
app.debug=True app.debug=True
app.run('127.0.0.1') app.run(DEV_LISTEN_HOST, DEV_LISTEN_PORT)

View File

@@ -4,6 +4,7 @@ from __future__ import absolute_import
import os import os
import logging import logging
import collections import collections
import urllib
from flask import ( from flask import (
Flask, render_template, abort, url_for, Flask, render_template, abort, url_for,
@@ -11,7 +12,6 @@ from flask import (
) )
from pypuppetdb import connect from pypuppetdb import connect
from pypuppetdb.errors import ExperimentalDisabledError
from puppetboard.forms import QueryForm from puppetboard.forms import QueryForm
from puppetboard.utils import ( from puppetboard.utils import (
@@ -26,13 +26,13 @@ app.config.from_envvar('PUPPETBOARD_SETTINGS', silent=True)
app.secret_key = os.urandom(24) app.secret_key = os.urandom(24)
puppetdb = connect( puppetdb = connect(
api_version=app.config['PUPPETDB_API'],
host=app.config['PUPPETDB_HOST'], host=app.config['PUPPETDB_HOST'],
port=app.config['PUPPETDB_PORT'], port=app.config['PUPPETDB_PORT'],
ssl=app.config['PUPPETDB_SSL'], ssl=app.config['PUPPETDB_SSL'],
ssl_key=app.config['PUPPETDB_KEY'], ssl_key=app.config['PUPPETDB_KEY'],
ssl_cert=app.config['PUPPETDB_CERT'], ssl_cert=app.config['PUPPETDB_CERT'],
timeout=app.config['PUPPETDB_TIMEOUT'], timeout=app.config['PUPPETDB_TIMEOUT'],)
experimental=app.config['PUPPETDB_EXPERIMENTAL'])
numeric_level = getattr(logging, app.config['LOGLEVEL'].upper(), None) numeric_level = getattr(logging, app.config['LOGLEVEL'].upper(), None)
if not isinstance(numeric_level, int): if not isinstance(numeric_level, int):
@@ -51,13 +51,18 @@ def stream_template(template_name, **context):
def bad_request(e): def bad_request(e):
return render_template('400.html'), 400 return render_template('400.html'), 400
@app.errorhandler(403)
def bad_request(e):
return render_template('403.html'), 400
@app.errorhandler(404) @app.errorhandler(404)
def not_found(e): def not_found(e):
return render_template('404.html'), 404 return render_template('404.html'), 404
@app.errorhandler(412) @app.errorhandler(412)
def precond_failed(e): def precond_failed(e):
"""We're slightly abusing 412 to handle ExperimentalDisabled errors.""" """We're slightly abusing 412 to handle missing features
depending on the API version."""
return render_template('412.html'), 412 return render_template('412.html'), 412
@app.errorhandler(500) @app.errorhandler(500)
@@ -83,9 +88,9 @@ def index():
metrics = { metrics = {
'num_nodes': num_nodes['Value'], 'num_nodes': num_nodes['Value'],
'num_resources': num_resources['Value'], 'num_resources': num_resources['Value'],
'avg_resources_node': "{:10.6f}".format(avg_resources_node['Value']), 'avg_resources_node': "{0:10.6f}".format(avg_resources_node['Value']),
'mean_failed_commands': mean_failed_commands['MeanRate'], 'mean_failed_commands': mean_failed_commands['MeanRate'],
'mean_command_time': "{:10.6f}".format(mean_command_time['MeanRate']), 'mean_command_time': "{0:10.6f}".format(mean_command_time['MeanRate']),
} }
return render_template('index.html', metrics=metrics) return render_template('index.html', metrics=metrics)
@@ -111,7 +116,7 @@ def node(node_name):
""" """
node = get_or_abort(puppetdb.node, node_name) node = get_or_abort(puppetdb.node, node_name)
facts = node.facts() facts = node.facts()
if app.config['PUPPETDB_EXPERIMENTAL']: if app.config['PUPPETDB_API'] > 2:
reports = ten_reports(node.reports()) reports = ten_reports(node.reports())
else: else:
reports = iter([]) reports = iter([])
@@ -122,21 +127,21 @@ def node(node_name):
def reports(): def reports():
"""Doesn't do much yet but is meant to show something like the reports of """Doesn't do much yet but is meant to show something like the reports of
the last half our, something like that.""" the last half our, something like that."""
if app.config['PUPPETDB_EXPERIMENTAL']: if app.config['PUPPETDB_API'] > 2:
return render_template('reports.html') return render_template('reports.html')
else: else:
log.warn('Access to experimental endpoint not allowed.') log.warn('PuppetDB API prior to v3 cannot access reports.')
abort(412) abort(412)
@app.route('/reports/<node>') @app.route('/reports/<node>')
def reports_node(node): def reports_node(node):
"""Fetches all reports for a node and processes them eventually rendering """Fetches all reports for a node and processes them eventually rendering
a table displaying those reports.""" a table displaying those reports."""
if app.config['PUPPETDB_EXPERIMENTAL']: if app.config['PUPPETDB_API'] > 2:
reports = ten_reports(yield_or_stop( reports = ten_reports(yield_or_stop(
puppetdb.reports('["=", "certname", "{0}"]'.format(node)))) puppetdb.reports('["=", "certname", "{0}"]'.format(node))))
else: else:
log.warn('Access to experimental endpoint not allowed.') log.warn('PuppetDB API prior to v3 cannot access reports.')
abort(412) abort(412)
return render_template('reports_node.html', reports=reports, return render_template('reports_node.html', reports=reports,
nodename=node) nodename=node)
@@ -145,10 +150,10 @@ def reports_node(node):
def report(node, report_id): def report(node, report_id):
"""Displays a single report including all the events associated with that """Displays a single report including all the events associated with that
report and their status.""" report and their status."""
if app.config['PUPPETDB_EXPERIMENTAL']: if app.config['PUPPETDB_API'] > 2:
reports = puppetdb.reports('["=", "certname", "{0}"]'.format(node)) reports = puppetdb.reports('["=", "certname", "{0}"]'.format(node))
else: else:
log.warn('Access to experimental endpoint not allowed.') log.warn('PuppetDB API prior to v3 cannot access reports.')
abort(412) abort(412)
for report in reports: for report in reports:
@@ -179,9 +184,12 @@ def facts():
def fact(fact): def fact(fact):
"""Fetches the specific fact from PuppetDB and displays its value per """Fetches the specific fact from PuppetDB and displays its value per
node for which this fact is known.""" node for which this fact is known."""
# we can only consume the generator once, lists can be doubly consumed
# om nom nom
localfacts = [ f for f in yield_or_stop(puppetdb.facts(name=fact)) ]
return Response(stream_with_context(stream_template('fact.html', return Response(stream_with_context(stream_template('fact.html',
name=fact, name=fact,
facts=yield_or_stop(puppetdb.facts(name=fact))))) facts=localfacts)))
@app.route('/query', methods=('GET', 'POST')) @app.route('/query', methods=('GET', 'POST'))
def query(): def query():
@@ -190,9 +198,26 @@ def query():
of the possible exceptions are being handled just yet. This will return of the possible exceptions are being handled just yet. This will return
the JSON of the response or a message telling you what whent wrong / the JSON of the response or a message telling you what whent wrong /
why nothing was returned.""" why nothing was returned."""
form = QueryForm() if app.config['ENABLE_QUERY']:
if form.validate_on_submit(): form = QueryForm()
result = get_or_abort(puppetdb._query, form.endpoints.data, if form.validate_on_submit():
query='[{0}]'.format(form.query.data)) result = get_or_abort(puppetdb._query, form.endpoints.data,
return render_template('query.html', form=form, result=result) query='[{0}]'.format(form.query.data))
return render_template('query.html', form=form) return render_template('query.html', form=form, result=result)
return render_template('query.html', form=form)
else:
log.warn('Access to query interface disabled by administrator..')
abort(403)
@app.route('/metrics')
def metrics():
metrics = puppetdb._query('metrics', path='mbeans')
for key,value in metrics.iteritems():
metrics[key]=value.split('/')[3]
return render_template('metrics.html', metrics=sorted(metrics.items()))
@app.route('/metric/<metric>')
def metric(metric):
name = urllib.unquote(metric)
metric = puppetdb.metric(metric)
return render_template('metric.html', name=name, metric=sorted(metric.items()))

View File

@@ -4,5 +4,8 @@ PUPPETDB_SSL=False
PUPPETDB_KEY=None PUPPETDB_KEY=None
PUPPETDB_CERT=None PUPPETDB_CERT=None
PUPPETDB_TIMEOUT=20 PUPPETDB_TIMEOUT=20
PUPPETDB_EXPERIMENTAL=False PUPPETDB_API=3
DEV_LISTEN_HOST='127.0.0.1'
DEV_LISTEN_PORT=5000
ENABLE_QUERY=True
LOGLEVEL='info' LOGLEVEL='info'

1770
puppetboard/static/js/Chart.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
{% extends 'layout.html' %}
{% block row_fluid %}
<div class="container" style="margin-bottom:55px;">
<div class="row">
<div class="span12">
<h2>Permission Denied</h2>
<p>What you were looking for has been disabled by the administrator.</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -3,8 +3,8 @@
<div class="container" style="margin-bottom:55px;"> <div class="container" style="margin-bottom:55px;">
<div class="row"> <div class="row">
<div class="span12"> <div class="span12">
<h2>Experimental Disabled</h2> <h2>Feature unavailable</h2>
<p>You're trying to access a feature restricted to PuppetDB's Experimental API but haven't configured Puppetboard to allow this.</p> <p>You've configured Puppetboard with an API version that does not support this feature.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,7 +5,11 @@
<table class="filter-table table table-striped {% if condensed %}table-condensed{% endif%}" style="table-layout:fixed"> <table class="filter-table table table-striped {% if condensed %}table-condensed{% endif%}" style="table-layout:fixed">
<thead> <thead>
<tr> <tr>
{% if show_node %}
<th>Node</th>
{% else %}
<th>Fact</th> <th>Fact</th>
{% endif %}
<th>Value</th> <th>Value</th>
</tr> </tr>
</thead> </thead>
@@ -13,9 +17,9 @@
{% for fact in facts %} {% for fact in facts %}
<tr> <tr>
{% if show_node %} {% if show_node %}
<td>{{fact.node}}</td> <td><a href="{{url_for('node', node_name=fact.node)}}">{{fact.node}}</a></td>
{% else %} {% else %}
<td>{{fact.name}}</td> <td><a href="{{url_for('fact', fact=fact.name)}}">{{fact.name}}</a></td>
{% endif %} {% endif %}
<td style="word-wrap:break-word">{{fact.value}}</td> <td style="word-wrap:break-word">{{fact.value}}</td>
</tr> </tr>
@@ -23,6 +27,63 @@
</tbody> </tbody>
</table> </table>
{%- endmacro %} {%- endmacro %}
{% macro facts_graph(facts, autofocus=False, condensed=False, show_node=False, margin_top=20, margin_bottom=20) -%}
<script src="{{url_for('static', filename='js/Chart.js')}}"></script>
<canvas id="factChart" width="300" height="300"></canvas>
<script type="text/javascript">
var colors = ["#9B59B6", "#3498DB", "#2ECC71", "#1ABC9C", "#F1C40F", "#E67E22", "#E74C3C"];
var len_colors = colors.length;
var data = [
{% for fact in facts|groupby('value') %}
{
label: "{{ fact.grouper }}",
value: {{ fact.list|length }}
},
{% endfor %}
{
value: 0,
}
]
for (var i = 0; i < data.length; i++) {
data[i].color = colors[i % len_colors];
}
var sorted_data = data.sort(function(a,b) { return parseFloat(b.value) - parseFloat(a.value)});
var top7 = sorted_data.slice(0,7);
var bottom = data.slice(7, -1);
var bottom_sum = 0;
for (var i = 0; i < bottom.length; i++) {
bottom_sum += bottom[i].value;
}
top7.push({ label: "Other", value: bottom_sum, color: "#B30202" });
var ctx = document.getElementById("factChart").getContext("2d");
new Chart(ctx).Pie(top7);
</script>
{%- endmacro %}
{% macro facts_graph_value(facts, autofocus=False, condensed=False, show_node=False, margin_top=20, margin_bottom=20) -%}
<script src="{{url_for('static', filename='js/Chart.js')}}"></script>
<canvas id="factChart_value" width="300" height="300"></canvas>
<script type="text/javascript">
var colors = ["#9B59B6", "#3498DB", "#2ECC71", "#1ABC9C", "#F1C40F", "#E67E22", "#E74C3C"];
var len_colors = colors.length;
var data = [
{% for fact in facts|groupby('value') %}
{
label: "{{ fact.grouper }}",
value: {{ fact.list|length }}
},
{% endfor %}
{
value: 0,
}
]
for (var i = 0; i < data.length; i++) {
data[i].color = colors[i % len_colors];
}
var ctx = document.getElementById("factChart_value").getContext("2d");
new Chart(ctx).Pie(data.sort(function(a,b) { return parseInt(a.label) - parseInt(b.label)}));
</script>
{%- endmacro %}
{% macro reports_table(reports, nodename, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True) -%} {% macro reports_table(reports, nodename, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True) -%}
<div class="alert alert-info"> <div class="alert alert-info">

View File

@@ -2,5 +2,7 @@
{% import '_macros.html' as macros %} {% import '_macros.html' as macros %}
{% block content %} {% block content %}
<h1>{{name}}</h1> <h1>{{name}}</h1>
{{macros.facts_graph(facts, autofocus=True, show_node=True, margin_bottom=10)}}
{{macros.facts_graph_value(facts, autofocus=True, show_node=True, margin_bottom=10)}}
{{macros.facts_table(facts, autofocus=True, show_node=True, margin_bottom=10)}} {{macros.facts_table(facts, autofocus=True, show_node=True, margin_bottom=10)}}
{% endblock content %} {% endblock content %}

View File

@@ -25,6 +25,7 @@
('nodes', 'Nodes'), ('nodes', 'Nodes'),
('facts', 'Facts'), ('facts', 'Facts'),
('reports', 'Reports'), ('reports', 'Reports'),
('metrics', 'Metrics'),
('query', 'Query'), ('query', 'Query'),
] %} ] %}
<li{% if endpoint == request.endpoint %} class=active{% endif <li{% if endpoint == request.endpoint %} class=active{% endif

View File

@@ -0,0 +1,18 @@
{% extends 'layout.html' %}
{% block content %}
<div class="page-header">
<h1>Metric
<small>{{name}}</small>
</h1>
</div>
<table class="table table-striped">
<tbody>
{% for key,value in metric %}
<tr>
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}

View File

@@ -0,0 +1,9 @@
{% extends 'layout.html' %}
{% block content %}
<h1>Metrics</h1>
<ul>
{% for key,value in metrics %}
<li><a href="{{url_for('metric', metric=value)}}">{{key}}</li>
{% endfor %}
</ul>
{% endblock content %}

View File

@@ -27,7 +27,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% if config.PUPPETDB_EXPERIMENTAL %} {% if config.PUPPETDB_API > 2 %}
<div class="span4"> <div class="span4">
<h1>Facts</h1> <h1>Facts</h1>
{{macros.facts_table(facts, condensed=True, margin_top=10)}} {{macros.facts_table(facts, condensed=True, margin_top=10)}}

View File

@@ -20,7 +20,7 @@
<tr> <tr>
<th>Hostname</th> <th>Hostname</th>
<th>Catalog compiled at</th> <th>Catalog compiled at</th>
{% if config.PUPPETDB_EXPERIMENTAL %} {% if config.PUPPETDB_API > 2 %}
<th>Last report</th> <th>Last report</th>
<th>&nbsp;</th> <th>&nbsp;</th>
{% endif %} {% endif %}
@@ -31,7 +31,7 @@
<tr> <tr>
<td><a href="{{url_for('node', node_name=node.name)}}">{{node.name}}</a></td> <td><a href="{{url_for('node', node_name=node.name)}}">{{node.name}}</a></td>
<td rel="utctimestamp">{{node.catalog_timestamp}}</td> <td rel="utctimestamp">{{node.catalog_timestamp}}</td>
{% if config.PUPPETDB_EXPERIMENTAL %} {% if config.PUPPETDB_API > 2 %}
<td> <td>
{% if node.report_timestamp %} {% if node.report_timestamp %}
<span rel="utctimestamp">{{ node.report_timestamp }}</span> <span rel="utctimestamp">{{ node.report_timestamp }}</span>
@@ -40,7 +40,9 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if node.report_timestamp %}
<a class="btn btn-small btn-primary" href="{{url_for('reports_node', node=node.name)}}">Reports</a> <a class="btn btn-small btn-primary" href="{{url_for('reports_node', node=node.name)}}">Reports</a>
{% endif %}
</td> </td>
{% endif %} {% endif %}
</tr> </tr>

View File

@@ -2,7 +2,7 @@
{% block row_fluid %} {% block row_fluid %}
<div class="span12"> <div class="span12">
<div class="alert"> <div class="alert">
This is highly exeprimental and will likely set your server on fire. This is highly experimental and will likely set your server on fire.
</div> </div>
</div> </div>
<div class="container" style="margin-bottom:55px;"> <div class="container" style="margin-bottom:55px;">
@@ -23,7 +23,7 @@
<div class="control-group {% if form.query.errors %} error {% endif %}"> <div class="control-group {% if form.query.errors %} error {% endif %}">
{{form.query.label(class_="control-label")}} {{form.query.label(class_="control-label")}}
<div class="controls"> <div class="controls">
{{form.query(class_="input-block-level", autofocus="autofocus", rows=5, placeholder="\"=\", \"name\" \"hostname\"")}} {{form.query(class_="input-block-level", autofocus="autofocus", rows=5, placeholder="\"=\", \"name\", \"hostname\"")}}
{% if form.query.errors %} {% if form.query.errors %}
<span class="help-inline">{% for error in form.query.errors %}{{error}}{% endfor %}</span> <span class="help-inline">{% for error in form.query.errors %}{{error}}{% endfor %}</span>
{% endif %} {% endif %}

View File

@@ -2,9 +2,9 @@ from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from requests.exceptions import HTTPError, ConnectionError from requests.exceptions import HTTPError, ConnectionError
from pypuppetdb.errors import EmptyResponseError, ExperimentalDisabledError from pypuppetdb.errors import EmptyResponseError
from flask import abort, flash from flask import abort
def get_or_abort(func, *args, **kwargs): def get_or_abort(func, *args, **kwargs):
"""Execute the function with its arguments and handle the possible """Execute the function with its arguments and handle the possible
@@ -18,8 +18,6 @@ def get_or_abort(func, *args, **kwargs):
abort(e.response.status_code) abort(e.response.status_code)
except ConnectionError: except ConnectionError:
abort(500) abort(500)
except ExperimentalDisabledError:
abort(412)
except EmptyResponseError: except EmptyResponseError:
abort(204) abort(204)
@@ -40,22 +38,12 @@ def yield_or_stop(generator):
generators and handle certain errors. generators and handle certain errors.
Since this is also used in streaming responses where we can't just abort Since this is also used in streaming responses where we can't just abort
a request we always yield empty and then raise StopIteration. a request we raise StopIteration.
""" """
while True: while True:
try: try:
yield next(generator) yield next(generator)
except StopIteration: except StopIteration:
raise raise
except ExperimentalDisabledError: except (EmptyResponseError, ConnectionError, HTTPError):
yield
raise StopIteration
except EmptyResponseError:
yield
raise StopIteration
except ConnectionError:
yield
raise StopIteration
except HTTPError:
yield
raise StopIteration raise StopIteration

View File

@@ -1,3 +1,9 @@
Flask==0.10.1 Flask==0.10.1
Flask-WTF==0.8.4 Flask-WTF==0.8.4
pypuppetdb=0.0.1 Jinja2==2.7
MarkupSafe==0.18
WTForms==1.0.4
Werkzeug==0.9.3
itsdangerous==0.22
pypuppetdb==0.0.4
requests==1.2.3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 KiB

After

Width:  |  Height:  |  Size: 170 KiB

BIN
screenshots/metric.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
screenshots/metrics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 241 KiB

After

Width:  |  Height:  |  Size: 241 KiB

View File

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 276 KiB