puppetboard: Commit the current code.
This is all the code after a week and a bit hacking on this project. It's in a rather experimental state but should work with a little effort.
This commit is contained in:
8
dev.py
Normal file
8
dev.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from puppetboard.app import app
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.debug=True
|
||||||
|
app.run('127.0.0.1')
|
||||||
0
puppetboard/__init__.py
Normal file
0
puppetboard/__init__.py
Normal file
198
puppetboard/app.py
Normal file
198
puppetboard/app.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import collections
|
||||||
|
|
||||||
|
from flask import (
|
||||||
|
Flask, render_template, abort, url_for,
|
||||||
|
Response, stream_with_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pypuppetdb import connect
|
||||||
|
from pypuppetdb.errors import ExperimentalDisabledError
|
||||||
|
|
||||||
|
from puppetboard.forms import QueryForm
|
||||||
|
from puppetboard.utils import (
|
||||||
|
get_or_abort, yield_or_stop,
|
||||||
|
ten_reports,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('puppetboard.default_settings')
|
||||||
|
app.config.from_envvar('PUPPETBOARD_SETTINGS', silent=True)
|
||||||
|
app.secret_key = os.urandom(24)
|
||||||
|
|
||||||
|
puppetdb = connect(
|
||||||
|
host=app.config['PUPPETDB_HOST'],
|
||||||
|
port=app.config['PUPPETDB_PORT'],
|
||||||
|
ssl=app.config['PUPPETDB_SSL'],
|
||||||
|
ssl_key=app.config['PUPPETDB_KEY'],
|
||||||
|
ssl_cert=app.config['PUPPETDB_CERT'],
|
||||||
|
timeout=app.config['PUPPETDB_TIMEOUT'],
|
||||||
|
experimental=app.config['PUPPETDB_EXPERIMENTAL'])
|
||||||
|
|
||||||
|
numeric_level = getattr(logging, app.config['LOGLEVEL'].upper(), None)
|
||||||
|
if not isinstance(numeric_level, int):
|
||||||
|
raise ValueError('Invalid log level: %s' % loglevel)
|
||||||
|
logging.basicConfig(level=numeric_level)
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def stream_template(template_name, **context):
|
||||||
|
app.update_template_context(context)
|
||||||
|
t = app.jinja_env.get_template(template_name)
|
||||||
|
rv = t.stream(context)
|
||||||
|
rv.enable_buffering(5)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
@app.errorhandler(400)
|
||||||
|
def bad_request(e):
|
||||||
|
return render_template('400.html'), 400
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def not_found(e):
|
||||||
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
@app.errorhandler(412)
|
||||||
|
def precond_failed(e):
|
||||||
|
"""We're slightly abusing 412 to handle ExperimentalDisabled errors."""
|
||||||
|
return render_template('412.html'), 412
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def server_error(e):
|
||||||
|
return render_template('500.html'), 500
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
"""This view generates the index page and displays a set of metrics fetched
|
||||||
|
from PuppetDB."""
|
||||||
|
# TODO: Would be great if we could parallelize this somehow, doing these
|
||||||
|
# requests in sequence is rather pointless.
|
||||||
|
num_nodes = get_or_abort(puppetdb.metric,
|
||||||
|
'com.puppetlabs.puppetdb.query.population:type=default,name=num-nodes')
|
||||||
|
num_resources = get_or_abort(puppetdb.metric,
|
||||||
|
'com.puppetlabs.puppetdb.query.population:type=default,name=num-resources')
|
||||||
|
avg_resources_node = get_or_abort(puppetdb.metric,
|
||||||
|
'com.puppetlabs.puppetdb.query.population:type=default,name=avg-resources-per-node')
|
||||||
|
mean_failed_commands = get_or_abort(puppetdb.metric,
|
||||||
|
'com.puppetlabs.puppetdb.command:type=global,name=fatal')
|
||||||
|
mean_command_time = get_or_abort(puppetdb.metric,
|
||||||
|
'com.puppetlabs.puppetdb.command:type=global,name=processing-time')
|
||||||
|
metrics = {
|
||||||
|
'num_nodes': num_nodes['Value'],
|
||||||
|
'num_resources': num_resources['Value'],
|
||||||
|
'avg_resources_node': "{:10.6f}".format(avg_resources_node['Value']),
|
||||||
|
'mean_failed_commands': mean_failed_commands['MeanRate'],
|
||||||
|
'mean_command_time': "{:10.6f}".format(mean_command_time['MeanRate']),
|
||||||
|
}
|
||||||
|
return render_template('index.html', metrics=metrics)
|
||||||
|
|
||||||
|
@app.route('/nodes')
|
||||||
|
def nodes():
|
||||||
|
"""Fetch all (active) nodes from PuppetDB and stream a table displaying
|
||||||
|
those nodes.
|
||||||
|
|
||||||
|
Downside of the streaming aproach is that since we've already sent our
|
||||||
|
headers we can't abort the request if we detect an error. Because of this
|
||||||
|
we'll end up with an empty table instead because of how yield_or_stop
|
||||||
|
works. Once pagination is in place we can change this but we'll need to
|
||||||
|
provide a search feature instead.
|
||||||
|
"""
|
||||||
|
return Response(stream_with_context(stream_template('nodes.html',
|
||||||
|
nodes=yield_or_stop(puppetdb.nodes()))))
|
||||||
|
|
||||||
|
@app.route('/node/<node_name>')
|
||||||
|
def node(node_name):
|
||||||
|
"""Display a dashboard for a node showing as much data as we have on that
|
||||||
|
node. This includes facts and reports but not Resources as that is too
|
||||||
|
heavy to do within a single request.
|
||||||
|
"""
|
||||||
|
node = get_or_abort(puppetdb.node, node_name)
|
||||||
|
facts = node.facts()
|
||||||
|
if app.config['PUPPETDB_EXPERIMENTAL']:
|
||||||
|
reports = ten_reports(node.reports())
|
||||||
|
else:
|
||||||
|
reports = iter([])
|
||||||
|
return render_template('node.html', node=node, facts=yield_or_stop(facts),
|
||||||
|
reports=yield_or_stop(reports))
|
||||||
|
|
||||||
|
@app.route('/reports')
|
||||||
|
def reports():
|
||||||
|
"""Doesn't do much yet but is meant to show something like the reports of
|
||||||
|
the last half our, something like that."""
|
||||||
|
if app.config['PUPPETDB_EXPERIMENTAL']:
|
||||||
|
return render_template('reports.html')
|
||||||
|
else:
|
||||||
|
log.warn('Access to experimental endpoint not allowed.')
|
||||||
|
abort(412)
|
||||||
|
|
||||||
|
@app.route('/reports/<node>')
|
||||||
|
def reports_node(node):
|
||||||
|
"""Fetches all reports for a node and processes them eventually rendering
|
||||||
|
a table displaying those reports."""
|
||||||
|
if app.config['PUPPETDB_EXPERIMENTAL']:
|
||||||
|
reports = ten_reports(yield_or_stop(
|
||||||
|
puppetdb.reports('["=", "certname", "{0}"]'.format(node))))
|
||||||
|
else:
|
||||||
|
log.warn('Access to experimental endpoint not allowed.')
|
||||||
|
abort(412)
|
||||||
|
return render_template('reports_node.html', reports=reports,
|
||||||
|
nodename=node)
|
||||||
|
|
||||||
|
@app.route('/report/<node>/<report_id>')
|
||||||
|
def report(node, report_id):
|
||||||
|
"""Displays a single report including all the events associated with that
|
||||||
|
report and their status."""
|
||||||
|
if app.config['PUPPETDB_EXPERIMENTAL']:
|
||||||
|
reports = puppetdb.reports('["=", "certname", "{0}"]'.format(node))
|
||||||
|
else:
|
||||||
|
log.warn('Access to experimental endpoint not allowed.')
|
||||||
|
abort(412)
|
||||||
|
|
||||||
|
for report in reports:
|
||||||
|
if report.hash_ == report_id:
|
||||||
|
events = puppetdb.events('["=", "report", "{0}"]'.format(
|
||||||
|
report.hash_))
|
||||||
|
return render_template('report.html', report=report,
|
||||||
|
events=yield_or_stop(events))
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
@app.route('/facts')
|
||||||
|
def facts():
|
||||||
|
"""Displays an alphabetical list of all facts currently known to
|
||||||
|
PuppetDB."""
|
||||||
|
facts_dict = collections.defaultdict(list)
|
||||||
|
facts = get_or_abort(puppetdb.fact_names)
|
||||||
|
for fact in facts:
|
||||||
|
letter = fact[0].upper()
|
||||||
|
letter_list = facts_dict[letter]
|
||||||
|
letter_list.append(fact)
|
||||||
|
facts_dict[letter] = letter_list
|
||||||
|
|
||||||
|
sorted_facts_dict = sorted(facts_dict.items())
|
||||||
|
return render_template('facts.html', facts_dict=sorted_facts_dict)
|
||||||
|
|
||||||
|
@app.route('/fact/<fact>')
|
||||||
|
def fact(fact):
|
||||||
|
"""Fetches the specific fact from PuppetDB and displays its value per
|
||||||
|
node for which this fact is known."""
|
||||||
|
return Response(stream_with_context(stream_template('fact.html',
|
||||||
|
name=fact,
|
||||||
|
facts=yield_or_stop(puppetdb.facts(name=fact)))))
|
||||||
|
|
||||||
|
@app.route('/query', methods=('GET', 'POST'))
|
||||||
|
def query():
|
||||||
|
"""Allows to execute raw, user created querries against PuppetDB. This is
|
||||||
|
currently highly experimental and explodes in interesting ways since none
|
||||||
|
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 /
|
||||||
|
why nothing was returned."""
|
||||||
|
form = QueryForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
result = get_or_abort(puppetdb._query, form.endpoints.data,
|
||||||
|
query='[{0}]'.format(form.query.data))
|
||||||
|
return render_template('query.html', form=form, result=result)
|
||||||
|
return render_template('query.html', form=form)
|
||||||
8
puppetboard/default_settings.py
Normal file
8
puppetboard/default_settings.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
PUPPETDB_HOST='localhost'
|
||||||
|
PUPPETDB_PORT=8080
|
||||||
|
PUPPETDB_SSL=False
|
||||||
|
PUPPETDB_KEY=None
|
||||||
|
PUPPETDB_CERT=None
|
||||||
|
PUPPETDB_TIMEOUT=20
|
||||||
|
PUPPETDB_EXPERIMENTAL=False
|
||||||
|
LOGLEVEL='info'
|
||||||
20
puppetboard/forms.py
Normal file
20
puppetboard/forms.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from flask.ext.wtf import Form
|
||||||
|
from wtforms import RadioField, TextAreaField, validators
|
||||||
|
|
||||||
|
class QueryForm(Form):
|
||||||
|
"""The form used to allow freeform queries to be executed against
|
||||||
|
PuppetDB."""
|
||||||
|
query = TextAreaField('Query', [validators.Required(
|
||||||
|
message='A query is required.')])
|
||||||
|
endpoints = RadioField('API endpoint', choices = [
|
||||||
|
('nodes', 'Nodes'),
|
||||||
|
('resources', 'Resources'),
|
||||||
|
('facts', 'Facts'),
|
||||||
|
('fact-names', 'Fact Names'),
|
||||||
|
('reports', 'Reports'),
|
||||||
|
('events', 'Events'),
|
||||||
|
])
|
||||||
|
|
||||||
17
puppetboard/static/coffeescript/lists.coffee
Normal file
17
puppetboard/static/coffeescript/lists.coffee
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
$ = jQuery
|
||||||
|
$ ->
|
||||||
|
$('input.filter-list').parent('div').removeClass('hide')
|
||||||
|
$("input.filter-list").on "keyup", (e) ->
|
||||||
|
rex = new RegExp($(this).val(), "i")
|
||||||
|
|
||||||
|
$(".searchable li").hide()
|
||||||
|
$(".searchable li").filter( ->
|
||||||
|
rex.test $(this).text()
|
||||||
|
).show()
|
||||||
|
|
||||||
|
if e.keyCode is 27
|
||||||
|
$(e.currentTarget).val ""
|
||||||
|
ev = $.Event("keyup")
|
||||||
|
ev.keyCode = 13
|
||||||
|
$(e.currentTarget).trigger(ev)
|
||||||
|
e.currentTarget.blur()
|
||||||
28
puppetboard/static/coffeescript/tables.coffee
Normal file
28
puppetboard/static/coffeescript/tables.coffee
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
$ = jQuery
|
||||||
|
$ ->
|
||||||
|
$('.nodes').tablesorter(
|
||||||
|
headers:
|
||||||
|
3:
|
||||||
|
sorter: false
|
||||||
|
sortList: [[0,0]]
|
||||||
|
)
|
||||||
|
|
||||||
|
$('.facts').tablesorter(
|
||||||
|
sortList: [[0,0]]
|
||||||
|
)
|
||||||
|
|
||||||
|
$('input.filter-table').parent('div').removeClass('hide')
|
||||||
|
$("input.filter-table").on "keyup", (e) ->
|
||||||
|
rex = new RegExp($(this).val(), "i")
|
||||||
|
|
||||||
|
$(".searchable tr").hide()
|
||||||
|
$(".searchable tr").filter( ->
|
||||||
|
rex.test $(this).text()
|
||||||
|
).show()
|
||||||
|
|
||||||
|
if e.keyCode is 27
|
||||||
|
$(e.currentTarget).val ""
|
||||||
|
ev = $.Event("keyup")
|
||||||
|
ev.keyCode = 13
|
||||||
|
$(e.currentTarget).trigger(ev)
|
||||||
|
e.currentTarget.blur()
|
||||||
51
puppetboard/static/css/puppetboard.css
Normal file
51
puppetboard/static/css/puppetboard.css
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
body {
|
||||||
|
padding-top: 60px;
|
||||||
|
}
|
||||||
|
th.headerSortUp {
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
th.headerSortDown {
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
th.header {
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
th.header:after {
|
||||||
|
content: "\f0dc";
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: inherit;
|
||||||
|
color: #000;
|
||||||
|
font-size: 18px;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
th.headerSortUp:after {
|
||||||
|
content: "\f0de";
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: inherit;
|
||||||
|
color: #000;
|
||||||
|
font-size: 18px;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
th.headerSortDown:after {
|
||||||
|
content: "\f0dd";
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: inherit;
|
||||||
|
color: #000;
|
||||||
|
font-size: 18px;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
.stat {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.navbar .brand:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
27
puppetboard/static/js/lists.js
Normal file
27
puppetboard/static/js/lists.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Generated by CoffeeScript 1.6.3
|
||||||
|
(function() {
|
||||||
|
var $;
|
||||||
|
|
||||||
|
$ = jQuery;
|
||||||
|
|
||||||
|
$(function() {});
|
||||||
|
|
||||||
|
$('input.filter-list').parent('div').removeClass('hide');
|
||||||
|
|
||||||
|
$("input.filter-list").on("keyup", function(e) {
|
||||||
|
var ev, rex;
|
||||||
|
rex = new RegExp($(this).val(), "i");
|
||||||
|
$(".searchable li").hide();
|
||||||
|
$(".searchable li").filter(function() {
|
||||||
|
return rex.test($(this).text());
|
||||||
|
}).show();
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
$(e.currentTarget).val("");
|
||||||
|
ev = $.Event("keyup");
|
||||||
|
ev.keyCode = 13;
|
||||||
|
$(e.currentTarget).trigger(ev);
|
||||||
|
return e.currentTarget.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
1400
puppetboard/static/js/moment.js
Normal file
1400
puppetboard/static/js/moment.js
Normal file
File diff suppressed because it is too large
Load Diff
40
puppetboard/static/js/tables.js
Normal file
40
puppetboard/static/js/tables.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Generated by CoffeeScript 1.6.3
|
||||||
|
(function() {
|
||||||
|
var $;
|
||||||
|
|
||||||
|
$ = jQuery;
|
||||||
|
|
||||||
|
$(function() {});
|
||||||
|
|
||||||
|
$('.nodes').tablesorter({
|
||||||
|
headers: {
|
||||||
|
3: {
|
||||||
|
sorter: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sortList: [[0, 0]]
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.facts').tablesorter({
|
||||||
|
sortList: [[0, 0]]
|
||||||
|
});
|
||||||
|
|
||||||
|
$('input.filter-table').parent('div').removeClass('hide');
|
||||||
|
|
||||||
|
$("input.filter-table").on("keyup", function(e) {
|
||||||
|
var ev, rex;
|
||||||
|
rex = new RegExp($(this).val(), "i");
|
||||||
|
$(".searchable tr").hide();
|
||||||
|
$(".searchable tr").filter(function() {
|
||||||
|
return rex.test($(this).text());
|
||||||
|
}).show();
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
$(e.currentTarget).val("");
|
||||||
|
ev = $.Event("keyup");
|
||||||
|
ev.keyCode = 13;
|
||||||
|
$(e.currentTarget).trigger(ev);
|
||||||
|
return e.currentTarget.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}).call(this);
|
||||||
23
puppetboard/static/js/timestamps.js
Normal file
23
puppetboard/static/js/timestamps.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
jQuery(function ($) {
|
||||||
|
var localise_timestamp = function(timestamp){
|
||||||
|
if (timestamp === "None"){
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
d = moment.utc(timestamp);
|
||||||
|
d.local();
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
|
$("[rel=utctimestamp]").each(
|
||||||
|
function(index, timestamp){
|
||||||
|
var tstamp = $(timestamp);
|
||||||
|
var tstring = tstamp.text().trim();
|
||||||
|
var result = localise_timestamp(tstring);
|
||||||
|
if (result == '') {
|
||||||
|
tstamp.text('Unknown');
|
||||||
|
} else {
|
||||||
|
tstamp.text(localise_timestamp(tstring).format('LLLL'));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
11
puppetboard/templates/400.html
Normal file
11
puppetboard/templates/400.html
Normal 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>Bad Request</h2>
|
||||||
|
<p>The request sent to PuppetDB was invalid. This is usually caused by using an unsupported operator.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
11
puppetboard/templates/404.html
Normal file
11
puppetboard/templates/404.html
Normal 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>Not Found</h2>
|
||||||
|
<p>What you were looking for could not be found in PuppetDB.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
11
puppetboard/templates/412.html
Normal file
11
puppetboard/templates/412.html
Normal 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>Experimental Disabled</h2>
|
||||||
|
<p>You're trying to access a feature restricted to PuppetDB's Experimental API but haven't configured Puppetboard to allow this.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
16
puppetboard/templates/500.html
Normal file
16
puppetboard/templates/500.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block row_fluid %}
|
||||||
|
<div class="container" style="margin-bottom:55px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<h2>Internal Server Error</h2>
|
||||||
|
<p>This error usually occurs because:
|
||||||
|
<ul>
|
||||||
|
<li>We were unable to reach PuppetDB;</li>
|
||||||
|
<Li>The query to be executed was malformed resulting in an incorrectly encoded request.</li>
|
||||||
|
</ul></p>
|
||||||
|
<p>Please have a look at the log output for further information.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
77
puppetboard/templates/_macros.html
Normal file
77
puppetboard/templates/_macros.html
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
{% macro facts_table(facts, autofocus=False, condensed=False, show_node=False, margin_top=20, margin_bottom=20) -%}
|
||||||
|
<div class="filter" style="margin-bottom:{{margin_bottom}}px;margin-top:{{margin_top}}px;">
|
||||||
|
<input {% if autofocus %} autofocus="autofocus" {% endif %} style="width:100%" type="text" class="filter-table input-medium search-query" placeholder="Type here to filter">
|
||||||
|
</div>
|
||||||
|
<table class="filter-table table table-striped {% if condensed %}table-condensed{% endif%}" style="table-layout:fixed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Fact</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="searchable">
|
||||||
|
{% for fact in facts %}
|
||||||
|
<tr>
|
||||||
|
{% if show_node %}
|
||||||
|
<td>{{fact.node}}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{fact.name}}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="word-wrap:break-word">{{fact.value}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% 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">
|
||||||
|
Only showing the last ten reports.
|
||||||
|
</div>
|
||||||
|
<table class='table table-striped {% if condensed %}table-condensed{% endif %}'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Start time</th>
|
||||||
|
<th>Run time</th>
|
||||||
|
<th>Full report</th>
|
||||||
|
{% if show_conf_col %}
|
||||||
|
<th>Configuration version</th>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_agent_col %}
|
||||||
|
<th>Agent version</th>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_host_col %}
|
||||||
|
<th>Hostname</th>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for report in reports %}
|
||||||
|
{% if hash_truncate %}
|
||||||
|
{% set rep_hash = "%s…"|format(report.hash_[0:6])|safe %}
|
||||||
|
{% else %}
|
||||||
|
{% set rep_hash = report.hash_ %}
|
||||||
|
{% endif %}
|
||||||
|
{% if report.failed %}
|
||||||
|
<tr class="error">
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
{% endif %}
|
||||||
|
<td rel="utctimestamp">{{report.start}}</td>
|
||||||
|
<td>{{report.run_time}}</td>
|
||||||
|
|
||||||
|
<td><a href="{{url_for('report', node=nodename, report_id=report.hash_)}}">{{rep_hash}}</a></td>
|
||||||
|
{% if show_conf_col %}
|
||||||
|
<td>{{report.version}}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_agent_col %}
|
||||||
|
<td>{{report.agent_version}}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_host_col %}
|
||||||
|
<td>{{nodename}}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{%- endmacro %}
|
||||||
6
puppetboard/templates/fact.html
Normal file
6
puppetboard/templates/fact.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% import '_macros.html' as macros %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{name}}</h1>
|
||||||
|
{{macros.facts_table(facts, autofocus=True, show_node=True, margin_bottom=10)}}
|
||||||
|
{% endblock content %}
|
||||||
16
puppetboard/templates/facts.html
Normal file
16
puppetboard/templates/facts.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="hide" style="margin-bottom:20px">
|
||||||
|
<input autofocus="autofocus" style="width:100%" type="text" class="filter-list input-medium search-query" placeholder="Type here to filter">
|
||||||
|
</div>
|
||||||
|
<div style="-moz-column-count:4; -webkit-column-count:4; column-count:4;">
|
||||||
|
{%- for key,facts_list in facts_dict %}
|
||||||
|
<span class='label label-success'>{{key}}</span>
|
||||||
|
<ul class="searchable">
|
||||||
|
{%- for fact in facts_list %}
|
||||||
|
<li><a href="{{url_for('fact', fact=fact)}}">{{fact}}</a></li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
38
puppetboard/templates/index.html
Normal file
38
puppetboard/templates/index.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block row_fluid %}
|
||||||
|
<div class="span12">
|
||||||
|
<div class='alert alert-info'>
|
||||||
|
We need something fancy here.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container" style="margin-bottom:55px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<div class="span4 stat">
|
||||||
|
<h1>{{metrics['num_nodes']}}</h1>
|
||||||
|
<span>Population</span>
|
||||||
|
</div>
|
||||||
|
<div class="span4 stat">
|
||||||
|
<h1>{{metrics['num_resources']}}</h1>
|
||||||
|
<span>Resources managed</span>
|
||||||
|
</div>
|
||||||
|
<div class="span4 stat">
|
||||||
|
<h1>{{metrics['avg_resources_node']}}</h1>
|
||||||
|
<span>Avg. resources/node</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<div class="span4 stat">
|
||||||
|
<h1>{{metrics['mean_failed_commands']}}</h1>
|
||||||
|
<span>Mean command failures</span>
|
||||||
|
</div>
|
||||||
|
<div class="span4 stat offset4">
|
||||||
|
<h1>{{metrics['mean_command_time']}}s</h1>
|
||||||
|
<span>Mean command execution time</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock row_fluid %}
|
||||||
66
puppetboard/templates/layout.html
Normal file
66
puppetboard/templates/layout.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Puppetᴃoard</title>
|
||||||
|
<link href="//netdna.bootstrapcdn.com/bootswatch/2.3.2/flatly/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
<link href="{{url_for('static', filename='css/puppetboard.css')}}" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="font-variant:small-caps" class="navbar navbar-fixed-top">
|
||||||
|
<div class="navbar-inner">
|
||||||
|
<div class="container">
|
||||||
|
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</a>
|
||||||
|
<span style="margin-top:-2px;" class="brand">Puppetboard</span>
|
||||||
|
<div class="nav-collapse collapse">
|
||||||
|
<ul class="nav">
|
||||||
|
{%- for endpoint, caption in [
|
||||||
|
('index', 'Overview'),
|
||||||
|
('nodes', 'Nodes'),
|
||||||
|
('facts', 'Facts'),
|
||||||
|
('reports', 'Reports'),
|
||||||
|
('query', 'Query'),
|
||||||
|
] %}
|
||||||
|
<li{% if endpoint == request.endpoint %} class=active{% endif
|
||||||
|
%}><a href="{{ url_for(endpoint) }}">{{ caption }}</a></li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% block container %}
|
||||||
|
<div class="container-fluid" style="margin-bottom:55px;">
|
||||||
|
<div class="row-fluid">
|
||||||
|
{% block row_fluid %}
|
||||||
|
<div class="span12">
|
||||||
|
{% block content %} {% endblock content %}
|
||||||
|
</div>
|
||||||
|
{% endblock row_fluid %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock container %}
|
||||||
|
<div class="navbar navbar-fixed-bottom" style="background-color:rgb(249, 249, 249);">
|
||||||
|
<div class="navbar" style="height:55px;margin-bottom:0;">
|
||||||
|
<div class="container-fluid" style="line-height:55px;">
|
||||||
|
Copyright © 2013 <a href="https://github.com/daenney">Daniele Sluijters</a>. <span style="float:right">Live from PuppetDB.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
|
||||||
|
<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
|
||||||
|
<script src="//cdn.jsdelivr.net/tablesorter/2.0.3/jquery.tablesorter.min.js"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/moment.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/timestamps.js')}}"></script>
|
||||||
|
<script src="{{url_for('static', filename='js/tables.js')}}"></script>
|
||||||
|
<script src="{{url_for('static', filename='js/lists.js')}}"></script>
|
||||||
|
{% block script %} {% endblock script %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
46
puppetboard/templates/node.html
Normal file
46
puppetboard/templates/node.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% import '_macros.html' as macros %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span4">
|
||||||
|
<h1>Details</h1>
|
||||||
|
<table class="table table-striped table-condensed" style="table-layout:fixed">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:140px">Hostname</td>
|
||||||
|
<td style="word-wrap:break-word"><b>{{node.name}}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Catalog compiled at</td>
|
||||||
|
<td rel="utctimestamp">{{node.catalog_timestamp}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Facts retrieved at</td>
|
||||||
|
<td rel="utctimestamp">{{node.facts_timestamp}}</td>
|
||||||
|
</tr>
|
||||||
|
{% if config.PUPPETDB_EXPERIMENTAL %}
|
||||||
|
<tr>
|
||||||
|
<td>Report uploaded at</td>
|
||||||
|
<td rel="utctimestamp">{{node.report_timestamp}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% if config.PUPPETDB_EXPERIMENTAL %}
|
||||||
|
<div class="span4">
|
||||||
|
<h1>Facts</h1>
|
||||||
|
{{macros.facts_table(facts, condensed=True, margin_top=10)}}
|
||||||
|
</div>
|
||||||
|
<div class="span4">
|
||||||
|
<h1>Reports</h1>
|
||||||
|
{{ macros.reports_table(reports, node.name, condensed=True, hash_truncate=True, show_conf_col=False, show_agent_col=False, show_host_col=False)}}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="span8">
|
||||||
|
<h1>Facts</h1>
|
||||||
|
{{macros.facts_table(facts, condensed=True, margin_top=10)}}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
50
puppetboard/templates/nodes.html
Normal file
50
puppetboard/templates/nodes.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
PuppetDB currently only returns active nodes.
|
||||||
|
</div>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{category}}">
|
||||||
|
{{message}}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<div class="hide" style="margin-bottom:20px">
|
||||||
|
<input autofocus="autofocus" style="width:100%" type="text" class="filter-table input-medium search-query" placeholder="Type here to filter">
|
||||||
|
</div>
|
||||||
|
<table class='nodes table table-striped table-condensed'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hostname</th>
|
||||||
|
<th>Catalog compiled at</th>
|
||||||
|
{% if config.PUPPETDB_EXPERIMENTAL %}
|
||||||
|
<th>Last report</th>
|
||||||
|
<th> </th>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="searchable">
|
||||||
|
{% for node in nodes %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{url_for('node', node_name=node.name)}}">{{node.name}}</a></td>
|
||||||
|
<td rel="utctimestamp">{{node.catalog_timestamp}}</td>
|
||||||
|
{% if config.PUPPETDB_EXPERIMENTAL %}
|
||||||
|
<td>
|
||||||
|
{% if node.report_timestamp %}
|
||||||
|
<span rel="utctimestamp">{{ node.report_timestamp }}</span>
|
||||||
|
{% else %}
|
||||||
|
<i class="icon icon-ban-circle"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn-small btn-primary" href="{{url_for('reports_node', node=node.name)}}">Reports</a>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock content %}
|
||||||
61
puppetboard/templates/query.html
Normal file
61
puppetboard/templates/query.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block row_fluid %}
|
||||||
|
<div class="span12">
|
||||||
|
<div class="alert">
|
||||||
|
This is highly exeprimental and will likely set your server on fire.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container" style="margin-bottom:55px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<h2>Compose</h2>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{category}}">
|
||||||
|
{{message}}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<form class="form-horizontal" method="POST" action="{{ url_for('query')}}">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="control-group {% if form.query.errors %} error {% endif %}">
|
||||||
|
{{form.query.label(class_="control-label")}}
|
||||||
|
<div class="controls">
|
||||||
|
{{form.query(class_="input-block-level", autofocus="autofocus", rows=5, placeholder="\"=\", \"name\" \"hostname\"")}}
|
||||||
|
{% if form.query.errors %}
|
||||||
|
<span class="help-inline">{% for error in form.query.errors %}{{error}}{% endfor %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group {% if form.endpoints.errors %} error {% endif %}">
|
||||||
|
{{form.endpoints.label(class_="control-label")}}
|
||||||
|
<div class="controls">
|
||||||
|
{% for subfield in form.endpoints %}
|
||||||
|
{{subfield.label(class_="radio inline")}}
|
||||||
|
{{subfield }}
|
||||||
|
{% endfor %}
|
||||||
|
{% if form.endpoints.errors %}
|
||||||
|
<span class="help-inline">{% for error in form.endpoints.errors %}{{error}}{% endfor %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">Yes I'm sure</button>
|
||||||
|
<button type="button" class="btn">No thanks</button>
|
||||||
|
</div>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if result %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="span12">
|
||||||
|
<h2>Result</h2>
|
||||||
|
<pre><code>{{ result|tojson|replace(", ", ",\n") }}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock row_fluid %}
|
||||||
62
puppetboard/templates/report.html
Normal file
62
puppetboard/templates/report.html
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Summary</h1>
|
||||||
|
<table class='table table-striped'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hostname</th>
|
||||||
|
<th>Configuration version</th>
|
||||||
|
<th>Start time</th>
|
||||||
|
<th>End time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{report.node}}</td>
|
||||||
|
<td>
|
||||||
|
{{report.version}}
|
||||||
|
</td>
|
||||||
|
<td rel="utctimestamp">
|
||||||
|
{{report.start}}
|
||||||
|
</td>
|
||||||
|
<td rel="utctimestamp">
|
||||||
|
{{report.end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h1>Events</h1>
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Resource</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Changed From</th>
|
||||||
|
<th>Changed To</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for event in events %}
|
||||||
|
{% if not event.failed and event.item['old'] != event.item['new'] %}
|
||||||
|
<tr class='success'>
|
||||||
|
{% elif event.failed %}
|
||||||
|
<tr class='error'>
|
||||||
|
{% endif %}
|
||||||
|
<td>{{event.item['type']}}[{{event.item['title']}}]</td>
|
||||||
|
<td>{{event.status}}</td>
|
||||||
|
<td>{{event.item['old']}}</td>
|
||||||
|
<td>{{event.item['new']}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
|
{% block script %}
|
||||||
|
<script type='text/javascript'>
|
||||||
|
jQuery(function ($) {
|
||||||
|
$("[rel=tooltip]").tooltip();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock script %}
|
||||||
6
puppetboard/templates/reports.html
Normal file
6
puppetboard/templates/reports.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert">
|
||||||
|
Pending <a href="http://projects.puppetlabs.com/issues/21600">#21600</a>. You can access reports for a node or individual reports through the <a href="{{url_for('nodes')}}">Nodes</a> tab.
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
5
puppetboard/templates/reports_node.html
Normal file
5
puppetboard/templates/reports_node.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% import '_macros.html' as macros %}
|
||||||
|
{% block content %}
|
||||||
|
{{ macros.reports_table(reports, nodename, condensed=False, hash_truncate=False, show_conf_col=True, show_agent_col=True, show_host_col=True)}}
|
||||||
|
{% endblock content %}
|
||||||
61
puppetboard/utils.py
Normal file
61
puppetboard/utils.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from requests.exceptions import HTTPError, ConnectionError
|
||||||
|
from pypuppetdb.errors import EmptyResponseError, ExperimentalDisabledError
|
||||||
|
|
||||||
|
from flask import abort, flash
|
||||||
|
|
||||||
|
def get_or_abort(func, *args, **kwargs):
|
||||||
|
"""Execute the function with its arguments and handle the possible
|
||||||
|
errors that might occur.
|
||||||
|
|
||||||
|
In this case, if we get an exception we simply abort the request.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except HTTPError, e:
|
||||||
|
abort(e.response.status_code)
|
||||||
|
except ConnectionError:
|
||||||
|
abort(500)
|
||||||
|
except ExperimentalDisabledError:
|
||||||
|
abort(412)
|
||||||
|
except EmptyResponseError:
|
||||||
|
abort(204)
|
||||||
|
|
||||||
|
|
||||||
|
def ten_reports(reports):
|
||||||
|
"""Helper to yield the first then reports from the reports generator.
|
||||||
|
|
||||||
|
This is an ugly solution at best...
|
||||||
|
"""
|
||||||
|
for count, report in enumerate(reports):
|
||||||
|
if count == 10:
|
||||||
|
raise StopIteration
|
||||||
|
yield report
|
||||||
|
|
||||||
|
|
||||||
|
def yield_or_stop(generator):
|
||||||
|
"""Similar in intent to get_or_abort this helper will iterate over our
|
||||||
|
generators and handle certain errors.
|
||||||
|
|
||||||
|
Since this is also used in streaming responses where we can't just abort
|
||||||
|
a request we always yield empty and then raise StopIteration.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
yield next(generator)
|
||||||
|
except StopIteration:
|
||||||
|
raise
|
||||||
|
except ExperimentalDisabledError:
|
||||||
|
yield
|
||||||
|
raise StopIteration
|
||||||
|
except EmptyResponseError:
|
||||||
|
yield
|
||||||
|
raise StopIteration
|
||||||
|
except ConnectionError:
|
||||||
|
yield
|
||||||
|
raise StopIteration
|
||||||
|
except HTTPError:
|
||||||
|
yield
|
||||||
|
raise StopIteration
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Flask==0.10.1
|
||||||
|
Flask-WTF==0.8.4
|
||||||
|
pypuppetdb=0.0.1
|
||||||
11
wsgi.py
Normal file
11
wsgi.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
me = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# Add us to the PYTHONPATH/sys.path if we're not on it
|
||||||
|
if not me in sys.path:
|
||||||
|
sys.path.insert(0, me)
|
||||||
|
|
||||||
|
from puppetboard.app import app as application
|
||||||
Reference in New Issue
Block a user