Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9334827826 | ||
|
|
7c44854988 | ||
|
|
3844d72bcb | ||
|
|
43ff09fbd4 | ||
|
|
0111e52096 | ||
|
|
beef893b6a | ||
|
|
7ed551dbe9 | ||
|
|
492e1057e0 | ||
|
|
e8bc0ccbb5 | ||
|
|
ddf08b9b75 | ||
|
|
fbe646f196 | ||
|
|
65c6251bac | ||
|
|
5874a58965 | ||
|
|
f9edda82b4 | ||
|
|
8fa0514585 | ||
|
|
fd29fe4261 | ||
|
|
3ebde245ed | ||
|
|
21fee5b775 | ||
|
|
8e4af7c034 | ||
|
|
370c514745 | ||
|
|
de22c61056 | ||
|
|
4226fdc368 | ||
|
|
43e37fdf64 | ||
|
|
9b8c8332ef | ||
|
|
40bd73415d | ||
|
|
596e850189 | ||
|
|
23b95dc1d2 | ||
|
|
cc9b3de2ec | ||
|
|
1c7363afa0 | ||
|
|
f5ff5b378d | ||
|
|
177f6c234a | ||
|
|
8ddec01ca0 | ||
|
|
26d7d43d17 | ||
|
|
f63a0cefcb | ||
|
|
05f14e3735 | ||
|
|
fff45e607a | ||
|
|
2c3ead77d2 | ||
|
|
b7fdfd8b0d | ||
|
|
e93db585e1 | ||
|
|
bfa4d1042e | ||
|
|
bba5d1dc15 | ||
|
|
8b0a797097 |
@@ -4,10 +4,42 @@ Changelog
|
||||
|
||||
This is the changelog for Puppetboard.
|
||||
|
||||
0.1.2
|
||||
====
|
||||
|
||||
* Add configuration option to set the default environment with new
|
||||
configuration option DEFAULT_ENVIRONMENT, defaults to 'production'.
|
||||
* Loading all available environments with every page load.
|
||||
* Adding an "All Environments" item to the Environments dropdown to
|
||||
remove all environment filters on PuppetDB data.
|
||||
* Updating README.rst to update links and describe new configuration
|
||||
options.
|
||||
* Fixing Query form submission problem by disabling CSRF protection.
|
||||
Needs to be re-implemented.
|
||||
* Updating the pypuppetdb requirement to >= 0.2.1, using information
|
||||
available in PuppetDB 3.2 and higher
|
||||
** latest_report_hash and latest_report_status fields from the Nodes
|
||||
endpoint, this effectively deprecates the report_latest() function
|
||||
** code_id from the Catalogs endpoint (current unused)
|
||||
* Adding a automatic refresh on the overview page to reload the page
|
||||
every X number of seconds, defaults to 30. This is configurable
|
||||
with the configuration option REFRESH_RATE
|
||||
* Fixing the table alignment in the catalog_compare() page by switching
|
||||
to fixed tables from basic tables.
|
||||
* Using colors similar to Puppet Dashboard and Foreman for the status
|
||||
counts sections
|
||||
|
||||
0.1.1
|
||||
====
|
||||
|
||||
* Fix bug where the reports template was not generating the report links
|
||||
with the right environment
|
||||
|
||||
0.1.0
|
||||
====
|
||||
|
||||
* Requires pypuppetdb >= 0.2.0
|
||||
* Drop support for PuppetDB 2 and earlier
|
||||
* Full support for PuppetDB 3.x
|
||||
* The first directory location is now a Puppet environment which is filtered
|
||||
on all supported queries. Users can browse different environments with a
|
||||
|
||||
79
README.rst
79
README.rst
@@ -8,6 +8,8 @@ functionality of `Puppet Dashboard`_.
|
||||
Puppetboard relies on the `pypuppetdb`_ library to fetch data from PuppetDB
|
||||
and is built with the help of the `Flask`_ microframework.
|
||||
|
||||
As of version 0.1.0 and higher, Puppetboard **requires** PuppetDB 3.
|
||||
|
||||
.. _pypuppetdb: https://pypi.python.org/pypi/pypuppetdb
|
||||
.. _PuppetDB: http://docs.puppetlabs.com/puppetdb/latest/index.html
|
||||
.. _Puppet Dashboard: http://docs.puppetlabs.com/dashboard/
|
||||
@@ -17,7 +19,7 @@ At the current time of writing, Puppetboard supports the following Python versio
|
||||
* Python 2.6
|
||||
* Python 2.7
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/overview.png
|
||||
.. image:: screenshots/overview.png
|
||||
:alt: View of a node
|
||||
:width: 1024
|
||||
:height: 700
|
||||
@@ -51,10 +53,10 @@ There is a `Puppet module`_ by `Spencer Krum`_ that takes care of installing Pup
|
||||
|
||||
You can install it with:
|
||||
|
||||
puppet module install nibalizer-puppetboard
|
||||
puppet module install puppet-puppetboard
|
||||
|
||||
.. _Spencer Krum: https://github.com/nibalizer
|
||||
.. _Puppet module: https://forge.puppetlabs.com/nibalizer/puppetboard
|
||||
.. _Puppet module: https://forge.puppetlabs.com/puppet/puppetboard
|
||||
|
||||
Manual
|
||||
^^^^^^
|
||||
@@ -69,7 +71,7 @@ This will install Puppetboard and take care of the dependencies. If you
|
||||
do this Puppetboard will be installed in the so called site-packages or
|
||||
dist-packages of your Python distribution.
|
||||
|
||||
The complete path on Debian and Ubuntu systems would be ``/usr/local/lib/pythonX.Y/lib/dist-packages/puppetboard`` and on Fedora would be ``/usr/lib/pythonX.Y/lib/site-packages/puppetboard``
|
||||
The complete path on Debian and Ubuntu systems would be ``/usr/local/lib/pythonX.Y/lib/dist-packages/puppetboard`` and on Fedora would be ``/usr/lib/pythonX.Y/site-packages/puppetboard``
|
||||
|
||||
where X and Y are replaced by your major and minor python versions.
|
||||
|
||||
@@ -172,10 +174,11 @@ connect. Therefor you'll also have to supply the following settings:
|
||||
For information about how to generate the correct keys please refer to the
|
||||
`pypuppetdb documentation`_.
|
||||
|
||||
Other settings that might be interesting:
|
||||
Other settings that might be interesting in no particular order:
|
||||
|
||||
* ``SECRET_KEY``: Refer to `Flask documentation`_, section sessions: How to
|
||||
generate good secret keys, to set the value.
|
||||
generate good secret keys, to set the value. Defaults to a random 24-char
|
||||
string generated by os.random(24)
|
||||
* ``PUPPETDB_TIMEOUT``: Defaults to 20 seconds but you might need to increase
|
||||
this value. It depends on how big the results are when querying PuppetDB.
|
||||
This behaviour will change in a future release when pagination will be
|
||||
@@ -188,6 +191,25 @@ Other settings that might be interesting:
|
||||
* ``ENABLE_QUERY``: Defaults to ``True`` causing a Query tab to show up in the
|
||||
web interface allowing users to write and execute arbitrary queries against
|
||||
a set of endpoints in PuppetDB. Change this to ``False`` to disable this.
|
||||
* ``GRAPH_FACTS``: A list of fact names to tell PuppetBoard to generate a
|
||||
pie-chart on the fact page. With some fact values being unique per node,
|
||||
like ipaddress, uuid, and serial number, as well as structured facts it was
|
||||
no longer feasible to generate a graph for everything.
|
||||
* ``INVENTORY_FACTS``: A list of tuples that serve as the column header and
|
||||
the fact name to search for to create the inventory page. If a fact is not
|
||||
found for a node then ``undef`` is printed.
|
||||
* ``ENABLE_CATALOG``: If set to ``True`` allows the user to view a node's
|
||||
latest catalog. This includes all managed resources, their file-system
|
||||
locations and their relationships, if available. Defaults to ``False``.
|
||||
* ``REFRESH_RATE``: Defaults to ``30`` the number of seconds to wait until
|
||||
the index page is automatically refreshed.
|
||||
* ``DEFAULT_ENVIRONMENT``: Defaults to ``'production'``, as the name
|
||||
suggests, load all information filtered by this environment value.
|
||||
* ``REPORTS_COUNT``: Defaults to ``10`` the limit of the number of reports
|
||||
to load on the node or any reports page.
|
||||
* ``OFFLINE_MODE``: If set to ``True`` load static assets (jquery,
|
||||
semantic-ui, tablesorter, etc) from the local web server instead of a CDN.
|
||||
Defaults to ``False``.
|
||||
|
||||
.. _pypuppetdb documentation: http://pypuppetdb.readthedocs.org/en/v0.1.0/quickstart.html#ssl
|
||||
.. _Flask documentation: http://flask.pocoo.org/docs/0.10/quickstart/#sessions
|
||||
@@ -238,7 +260,7 @@ First we need to create the necessary directories:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ mkdir -p /var/www/puppetboard
|
||||
$ mkdir -p /var/www/html/puppetboard
|
||||
|
||||
Copy Puppetboard's ``default_settings.py`` to the newly created puppetboard
|
||||
directory and name the file ``settings.py``. This file will be available
|
||||
@@ -260,7 +282,7 @@ puppetboard directory:
|
||||
import os
|
||||
|
||||
# Needed if a settings.py file exists
|
||||
os.environ['PUPPETBOARD_SETTINGS'] = '/var/www/puppetboard/settings.py'
|
||||
os.environ['PUPPETBOARD_SETTINGS'] = '/var/www/html/puppetboard/settings.py'
|
||||
from puppetboard.app import app as application
|
||||
|
||||
Make sure this file is readable by the user the webserver runs as.
|
||||
@@ -274,7 +296,7 @@ Here is a sample configuration for Debian and Ubuntu:
|
||||
<VirtualHost *:80>
|
||||
ServerName puppetboard.example.tld
|
||||
WSGIDaemonProcess puppetboard user=www-data group=www-data threads=5
|
||||
WSGIScriptAlias / /var/www/puppetboard/wsgi.py
|
||||
WSGIScriptAlias / /var/www/html/puppetboard/wsgi.py
|
||||
ErrorLog /var/log/apache2/puppetboard.error.log
|
||||
CustomLog /var/log/apache2/puppetboard.access.log combined
|
||||
|
||||
@@ -299,11 +321,11 @@ Here is a sample configuration for Fedora:
|
||||
<VirtualHost *:80>
|
||||
ServerName puppetboard.example.tld
|
||||
WSGIDaemonProcess puppetboard user=apache group=apache threads=5
|
||||
WSGIScriptAlias / /var/www/puppetboard/wsgi.py
|
||||
ErrorLog /var/log/httpd/puppetboard.error.log
|
||||
CustomLog /var/log/httpd/puppetboard.access.log combined
|
||||
WSGIScriptAlias / /var/www/html/puppetboard/wsgi.py
|
||||
ErrorLog logs/puppetboard-error_log
|
||||
CustomLog logs/puppetboard-access_log combined
|
||||
|
||||
Alias /static /usr/local/lib/pythonX.Y/site-packages/puppetboard/static
|
||||
Alias /static /usr/lib/pythonX.Y/site-packages/puppetboard/static
|
||||
<Directory /usr/lib/python2.X/site-packages/puppetboard/static>
|
||||
Satisfy Any
|
||||
Allow from all
|
||||
@@ -652,70 +674,79 @@ messages have a look at this post by `Tim Pope`_.
|
||||
|
||||
.. _Tim Pope: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
`vagrant-puppetboard`_
|
||||
|
||||
.. _vagrant-puppetboard: https://github.com/visibilityspots/vagrant-puppet/tree/puppetboard
|
||||
|
||||
A vagrant project to show off the puppetboard functionallity using the puppetboard puppet module on a puppetserver with puppetdb.
|
||||
|
||||
Screenshots
|
||||
===========
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/overview.png
|
||||
.. image:: screenshots/overview.png
|
||||
:alt: Overview / Index / Homepage
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/nodes.png
|
||||
.. image:: screenshots/nodes.png
|
||||
:alt: Nodes view, all active nodes
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/node.png
|
||||
.. image:: screenshots/node.png
|
||||
:alt: Single node page / overview
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/report.png
|
||||
.. image:: screenshots/report.png
|
||||
:alt: Report view
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/facts.png
|
||||
.. image:: screenshots/facts.png
|
||||
:alt: Facts view
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/fact.png
|
||||
.. image:: screenshots/fact.png
|
||||
:alt: Single fact, with graphs
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/fact_value.png
|
||||
.. image:: screenshots/fact_value.png
|
||||
:alt: All nodes that have this fact with that value
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/metrics.png
|
||||
.. image:: screenshots/metrics.png
|
||||
:alt: Metrics view
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/metric.png
|
||||
.. image:: screenshots/metric.png
|
||||
:alt: Single metric
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/query.png
|
||||
.. image:: screenshots/query.png
|
||||
:alt: Query view
|
||||
:width: 1024
|
||||
:height: 700
|
||||
:align: center
|
||||
|
||||
.. image:: https://raw.github.com/nedap/puppetboard/master/screenshots/broken.png
|
||||
.. image:: screenshots/broken.png
|
||||
:alt: Error page
|
||||
:width: 1024
|
||||
:height: 700
|
||||
|
||||
@@ -15,7 +15,6 @@ from flask import (
|
||||
Response, stream_with_context, redirect,
|
||||
request
|
||||
)
|
||||
from flask_wtf.csrf import CsrfProtect
|
||||
|
||||
from pypuppetdb import connect
|
||||
|
||||
@@ -27,7 +26,6 @@ from puppetboard.utils import (
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
CsrfProtect(app)
|
||||
|
||||
app.config.from_object('puppetboard.default_settings')
|
||||
graph_facts = app.config['GRAPH_FACTS']
|
||||
@@ -78,15 +76,13 @@ def environments():
|
||||
|
||||
return x
|
||||
|
||||
def check_env(env):
|
||||
if env not in envs:
|
||||
def check_env(env, envs):
|
||||
if env != '*' and env not in envs:
|
||||
abort(404)
|
||||
|
||||
app.jinja_env.globals['url_for_pagination'] = url_for_pagination
|
||||
app.jinja_env.globals['url_for_environments'] = url_for_environments
|
||||
|
||||
envs = environments()
|
||||
|
||||
@app.context_processor
|
||||
def utility_processor():
|
||||
def now(format='%m/%d/%Y %H:%M:%S'):
|
||||
@@ -97,16 +93,19 @@ def utility_processor():
|
||||
|
||||
@app.errorhandler(400)
|
||||
def bad_request(e):
|
||||
envs = environments()
|
||||
return render_template('400.html', envs=envs), 400
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden(e):
|
||||
envs = environments()
|
||||
return render_template('403.html', envs=envs), 400
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
envs = environments()
|
||||
return render_template('404.html', envs=envs), 404
|
||||
|
||||
|
||||
@@ -114,15 +113,17 @@ def not_found(e):
|
||||
def precond_failed(e):
|
||||
"""We're slightly abusing 412 to handle missing features
|
||||
depending on the API version."""
|
||||
envs = environments()
|
||||
return render_template('412.html', envs=envs), 412
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
def server_error(e):
|
||||
envs = environments()
|
||||
return render_template('500.html', envs=envs), 500
|
||||
|
||||
|
||||
@app.route('/', defaults={'env': 'production'})
|
||||
@app.route('/', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/')
|
||||
def index(env):
|
||||
"""This view generates the index page and displays a set of metrics and
|
||||
@@ -131,10 +132,16 @@ def index(env):
|
||||
:param env: Search for nodes in this (Catalog and Fact) environment
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
metrics = {
|
||||
'num_nodes': 0,
|
||||
'num_resources': 0,
|
||||
'avg_resources_node': 0}
|
||||
check_env(env, envs)
|
||||
|
||||
if env == '*':
|
||||
query = None
|
||||
|
||||
# TODO: Would be great if we could parallelize this somehow, doing these
|
||||
# requests in sequence is rather pointless.
|
||||
prefix = 'puppetlabs.puppetdb.query.population'
|
||||
num_nodes = get_or_abort(
|
||||
puppetdb.metric,
|
||||
@@ -145,16 +152,33 @@ def index(env):
|
||||
avg_resources_node = get_or_abort(
|
||||
puppetdb.metric,
|
||||
"{0}{1}".format(prefix, ':type=default,name=avg-resources-per-node'))
|
||||
metrics = {
|
||||
'num_nodes': num_nodes['Value'],
|
||||
'num_resources': num_resources['Value'],
|
||||
'avg_resources_node': "{0:10.0f}".format(avg_resources_node['Value']),
|
||||
}
|
||||
metrics['num_nodes'] = num_nodes['Value']
|
||||
metrics['num_resources'] = num_resources['Value']
|
||||
metrics['avg_resources_node'] = "{0:10.0f}".format(
|
||||
avg_resources_node['Value'])
|
||||
else:
|
||||
query = '["and", {0}]'.format(
|
||||
", ".join('["=", "{0}", "{1}"]'.format(field, env)
|
||||
for field in ['catalog_environment', 'facts_environment']))
|
||||
num_nodes = get_or_abort(
|
||||
puppetdb._query,
|
||||
'nodes',
|
||||
query='["extract", [["function", "count"]],["and", {0}]]'.format(
|
||||
",".join('["=", "{0}", "{1}"]'.format(field, env)
|
||||
for field in ['catalog_environment', 'facts_environment'])))
|
||||
num_resources = get_or_abort(
|
||||
puppetdb._query,
|
||||
'resources',
|
||||
query='["extract", [["function", "count"]],' \
|
||||
'["=", "environment", "{0}"]]'.format(
|
||||
env))
|
||||
metrics['num_nodes'] = num_nodes[0]['count']
|
||||
metrics['num_resources'] = num_resources[0]['count']
|
||||
metrics['avg_resources_node'] = "{0:10.0f}".format(
|
||||
(num_resources[0]['count'] / num_nodes[0]['count']))
|
||||
|
||||
nodes = get_or_abort(puppetdb.nodes,
|
||||
query='["and", {0}]'.format(
|
||||
", ".join('["=", "{0}", "{1}"]'.format(field, env)
|
||||
for field in ['catalog_environment', 'facts_environment'])),
|
||||
query=query,
|
||||
unreported=app.config['UNRESPONSIVE_HOURS'],
|
||||
with_status=True)
|
||||
|
||||
@@ -192,7 +216,7 @@ def index(env):
|
||||
)
|
||||
|
||||
|
||||
@app.route('/nodes', defaults={'env': 'production'})
|
||||
@app.route('/nodes', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/nodes')
|
||||
def nodes(env):
|
||||
"""Fetch all (active) nodes from PuppetDB and stream a table displaying
|
||||
@@ -207,13 +231,19 @@ def nodes(env):
|
||||
:param env: Search for nodes in this (Catalog and Fact) environment
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if env == '*':
|
||||
query = None
|
||||
else:
|
||||
query = '["and", {0}]'.format(
|
||||
", ".join('["=", "{0}", "{1}"]'.format(field, env)
|
||||
for field in ['catalog_environment', 'facts_environment'])),
|
||||
|
||||
status_arg = request.args.get('status', '')
|
||||
nodelist = puppetdb.nodes(
|
||||
query='["and", {0}]'.format(
|
||||
", ".join('["=", "{0}", "{1}"]'.format(field, env)
|
||||
for field in ['catalog_environment', 'facts_environment'])),
|
||||
query=query,
|
||||
unreported=app.config['UNRESPONSIVE_HOURS'],
|
||||
with_status=True)
|
||||
nodes = []
|
||||
@@ -230,7 +260,7 @@ def nodes(env):
|
||||
current_env=env)))
|
||||
|
||||
|
||||
@app.route('/inventory', defaults={'env': 'production'})
|
||||
@app.route('/inventory', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/inventory')
|
||||
def inventory(env):
|
||||
"""Fetch all (active) nodes from PuppetDB and stream a table displaying
|
||||
@@ -245,7 +275,8 @@ def inventory(env):
|
||||
:param env: Search for facts in this environment
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
fact_desc = [] # a list of fact descriptions to go
|
||||
# in the table header
|
||||
@@ -272,6 +303,11 @@ def inventory(env):
|
||||
fact_desc.append(description)
|
||||
fact_names.append(name)
|
||||
|
||||
if env == '*':
|
||||
query = '["or", {0}]]'.format(
|
||||
', '.join('["=", "name", "{0}"]'.format(name)
|
||||
for name in fact_names))
|
||||
else:
|
||||
query = '["and", ["=", "environment", "{0}"], ["or", {1}]]'.format(
|
||||
env,
|
||||
', '.join('["=", "name", "{0}"]'.format(name)
|
||||
@@ -302,7 +338,7 @@ def inventory(env):
|
||||
current_env=env)))
|
||||
|
||||
|
||||
@app.route('/node/<node_name>', defaults={'env': 'production'})
|
||||
@app.route('/node/<node_name>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/node/<node_name>')
|
||||
def node(env, node_name):
|
||||
"""Display a dashboard for a node showing as much data as we have on that
|
||||
@@ -312,13 +348,19 @@ def node(env, node_name):
|
||||
:param env: Ensure that the node, facts and reports are in this environment
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if env == '*':
|
||||
query = '["=", "certname", "{0}"]]'.format(node_name)
|
||||
else:
|
||||
query='["and", ["=", "environment", "{0}"],' \
|
||||
'["=", "certname", "{1}"]]'.format(env, node_name),
|
||||
|
||||
node = get_or_abort(puppetdb.node, node_name)
|
||||
facts = node.facts()
|
||||
reports = get_or_abort(puppetdb.reports,
|
||||
query='["and", ["=", "environment", "{0}"],' \
|
||||
'["=", "certname", "{1}"]]'.format(env, node_name),
|
||||
query=query,
|
||||
limit=app.config['REPORTS_COUNT'],
|
||||
order_by='[{"field": "start_time", "order": "desc"}]')
|
||||
reports, reports_events = tee(reports)
|
||||
@@ -347,7 +389,7 @@ def node(env, node_name):
|
||||
current_env=env)
|
||||
|
||||
|
||||
@app.route('/reports/', defaults={'env': 'production', 'page': 1})
|
||||
@app.route('/reports/', defaults={'env': app.config['DEFAULT_ENVIRONMENT'], 'page': 1})
|
||||
@app.route('/<env>/reports/', defaults={'page': 1})
|
||||
@app.route('/<env>/reports/page/<int:page>')
|
||||
def reports(env, page):
|
||||
@@ -360,18 +402,25 @@ def reports(env, page):
|
||||
and this value
|
||||
:type page: :obj:`int`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if env == '*':
|
||||
reports_query = None
|
||||
total_query = '["extract", [["function", "count"]], ["~", "certname", ""]]'
|
||||
else:
|
||||
reports_query = '["=", "environment", "{0}"]'.format(env)
|
||||
total_query = '["extract", [["function", "count"]],'\
|
||||
'["and", ["=", "environment", "{0}"]]]'.format(env)
|
||||
|
||||
reports = get_or_abort(puppetdb.reports,
|
||||
query='["=", "environment", "{0}"]'.format(env),
|
||||
query=reports_query,
|
||||
limit=app.config['REPORTS_COUNT'],
|
||||
offset=(page-1) * app.config['REPORTS_COUNT'],
|
||||
order_by='[{"field": "start_time", "order": "desc"}]')
|
||||
total = get_or_abort(puppetdb._query,
|
||||
'reports',
|
||||
query='["extract", [["function", "count"]],'\
|
||||
'["and", ["=", "environment", "{0}"]]]'.format(
|
||||
env))
|
||||
query=total_query)
|
||||
total = total[0]['count']
|
||||
reports, reports_events = tee(reports)
|
||||
report_event_counts = {}
|
||||
@@ -380,14 +429,22 @@ def reports(env, page):
|
||||
abort(404)
|
||||
|
||||
for report in reports_events:
|
||||
counts = get_or_abort(puppetdb.event_counts,
|
||||
query='["and",' \
|
||||
if env == '*':
|
||||
event_count_query = '["and",' \
|
||||
'["=", "certname", "{0}"],' \
|
||||
'["=", "report", "{1}"]]'.format(
|
||||
report.node,
|
||||
report.hash_)
|
||||
else:
|
||||
event_count_query = '["and",' \
|
||||
'["=", "environment", "{0}"],' \
|
||||
'["=", "certname", "{1}"],' \
|
||||
'["=", "report", "{2}"]]'.format(
|
||||
env,
|
||||
report.node,
|
||||
report.hash_),
|
||||
report.hash_)
|
||||
counts = get_or_abort(puppetdb.event_counts,
|
||||
query=event_count_query,
|
||||
summarize_by="certname")
|
||||
try:
|
||||
report_event_counts[report.hash_] = counts[0]
|
||||
@@ -403,7 +460,7 @@ def reports(env, page):
|
||||
current_env=env)))
|
||||
|
||||
|
||||
@app.route('/reports/<node_name>/', defaults={'env': 'production', 'page': 1})
|
||||
@app.route('/reports/<node_name>/', defaults={'env': app.config['DEFAULT_ENVIRONMENT'], 'page': 1})
|
||||
@app.route('/<env>/reports/<node_name>', defaults={'page': 1})
|
||||
@app.route('/<env>/reports/<node_name>/page/<int:page>')
|
||||
def reports_node(env, node_name, page):
|
||||
@@ -418,12 +475,18 @@ def reports_node(env, node_name, page):
|
||||
and this value
|
||||
:type page: :obj:`int`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
reports = get_or_abort(puppetdb.reports,
|
||||
if env == '*':
|
||||
query = '["=", "certname", "{0}"]]'.format(node_name)
|
||||
else:
|
||||
query='["and",' \
|
||||
'["=", "environment", "{0}"],' \
|
||||
'["=", "certname", "{1}"]]'.format(env, node_name),
|
||||
|
||||
reports = get_or_abort(puppetdb.reports,
|
||||
query=query,
|
||||
limit=app.config['REPORTS_COUNT'],
|
||||
offset=(page-1) * app.config['REPORTS_COUNT'],
|
||||
order_by='[{"field": "start_time", "order": "desc"}]')
|
||||
@@ -441,11 +504,22 @@ def reports_node(env, node_name, page):
|
||||
abort(404)
|
||||
|
||||
for report in reports_events:
|
||||
counts = get_or_abort(puppetdb.event_counts,
|
||||
query='["and",' \
|
||||
if env == '*':
|
||||
event_count_query = '["and",' \
|
||||
'["=", "certname", "{0}"],' \
|
||||
'["=", "report", "{1}"]]'.format(
|
||||
report.node,
|
||||
report.hash_)
|
||||
else:
|
||||
event_count_query = '["and",' \
|
||||
'["=", "environment", "{0}"],' \
|
||||
'["=", "certname", "{1}"],' \
|
||||
'["=", "report", "{2}"]]'.format(env, report.node, report.hash_),
|
||||
'["=", "report", "{2}"]]'.format(
|
||||
env,
|
||||
report.node,
|
||||
report.hash_)
|
||||
counts = get_or_abort(puppetdb.event_counts,
|
||||
query=event_count_query,
|
||||
summarize_by="certname")
|
||||
try:
|
||||
report_event_counts[report.hash_] = counts[0]
|
||||
@@ -461,7 +535,7 @@ def reports_node(env, node_name, page):
|
||||
current_env=env)
|
||||
|
||||
|
||||
@app.route('/report/latest/<node_name>', defaults={'env': 'production'})
|
||||
@app.route('/report/latest/<node_name>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/report/latest/<node_name>')
|
||||
def report_latest(env, node_name):
|
||||
"""Redirect to the latest report of a given node.
|
||||
@@ -471,25 +545,50 @@ def report_latest(env, node_name):
|
||||
:param node_name: Find the reports whose certname match this value
|
||||
:type node_name: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
reports = get_or_abort(puppetdb.reports,
|
||||
if env == '*':
|
||||
node_query = '["=", "certname", "{0}"]'.format(node_name)
|
||||
else:
|
||||
node_query = '["and",' \
|
||||
'["=", "report_environment", "{0}"],' \
|
||||
'["=", "certname", "{1}"]]'.format(env, node_name)
|
||||
|
||||
try:
|
||||
node = next(get_or_abort(puppetdb.nodes,
|
||||
query=node_query,
|
||||
with_status=True))
|
||||
except StopIteration:
|
||||
abort(404)
|
||||
|
||||
if node.latest_report_hash is not None:
|
||||
hash_ = node.latest_report_hash
|
||||
else:
|
||||
if env == '*':
|
||||
query='["and",' \
|
||||
'["=", "certname", "{0}"],' \
|
||||
'["=", "latest_report?", true]]'.format(node.name)
|
||||
else:
|
||||
query='["and",' \
|
||||
'["=", "environment", "{0}"],' \
|
||||
'["=", "certname", "{1}"],' \
|
||||
'["=", "latest_report?", true]]'.format(
|
||||
env,
|
||||
node_name))
|
||||
node.name)
|
||||
|
||||
reports = get_or_abort(puppetdb.reports, query=query)
|
||||
try:
|
||||
report = next(reports)
|
||||
hash_ = report.hash_
|
||||
except StopIteration:
|
||||
abort(404)
|
||||
|
||||
return redirect(
|
||||
url_for('report', env=env, node_name=node_name, report_id=report.hash_))
|
||||
url_for('report', env=env, node_name=node_name, report_id=hash_))
|
||||
|
||||
|
||||
@app.route('/report/<node_name>/<report_id>', defaults={'env': 'production'})
|
||||
@app.route('/report/<node_name>/<report_id>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/report/<node_name>/<report_id>')
|
||||
def report(env, node_name, report_id):
|
||||
"""Displays a single report including all the events associated with that
|
||||
@@ -507,8 +606,14 @@ def report(env, node_name, report_id):
|
||||
report
|
||||
:type report_id: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if env == '*':
|
||||
query = '["and", ["=", "certname", "{0}"],' \
|
||||
'["or", ["=", "hash", "{1}"], ["=", "configuration_version", "{1}"]]]'.format(
|
||||
node_name, report_id)
|
||||
else:
|
||||
query = '["and", ["=", "environment", "{0}"], ["=", "certname", "{1}"],' \
|
||||
'["or", ["=", "hash", "{2}"], ["=", "configuration_version", "{2}"]]]'.format(
|
||||
env, node_name, report_id)
|
||||
@@ -529,7 +634,7 @@ def report(env, node_name, report_id):
|
||||
current_env=env)
|
||||
|
||||
|
||||
@app.route('/facts', defaults={'env': 'production'})
|
||||
@app.route('/facts', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/facts')
|
||||
def facts(env):
|
||||
"""Displays an alphabetical list of all facts currently known to
|
||||
@@ -539,7 +644,8 @@ def facts(env):
|
||||
sake
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
facts_dict = collections.defaultdict(list)
|
||||
facts = get_or_abort(puppetdb.fact_names)
|
||||
@@ -556,7 +662,7 @@ def facts(env):
|
||||
current_env=env)
|
||||
|
||||
|
||||
@app.route('/fact/<fact>', defaults={'env': 'production'})
|
||||
@app.route('/fact/<fact>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/fact/<fact>')
|
||||
def fact(env, fact):
|
||||
"""Fetches the specific fact from PuppetDB and displays its value per
|
||||
@@ -567,16 +673,20 @@ def fact(env, fact):
|
||||
:param fact: Find all facts with this name
|
||||
:type fact: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
# we can only consume the generator once, lists can be doubly consumed
|
||||
# om nom nom
|
||||
render_graph = False
|
||||
if fact in graph_facts:
|
||||
render_graph = True
|
||||
if env == '*':
|
||||
query = None
|
||||
else:
|
||||
query = '["=", "environment", "{0}"]'.format(env)
|
||||
localfacts = [f for f in yield_or_stop(puppetdb.facts(
|
||||
name=fact,
|
||||
query='["=", "environment", "{0}"]'.format(env)))]
|
||||
name=fact, query=query))]
|
||||
return Response(stream_with_context(stream_template(
|
||||
'fact.html',
|
||||
name=fact,
|
||||
@@ -586,7 +696,7 @@ def fact(env, fact):
|
||||
current_env=env)))
|
||||
|
||||
|
||||
@app.route('/fact/<fact>/<value>', defaults={'env': 'production'})
|
||||
@app.route('/fact/<fact>/<value>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/fact/<fact>/<value>')
|
||||
def fact_value(env, fact, value):
|
||||
"""On asking for fact/value get all nodes with that fact.
|
||||
@@ -598,12 +708,17 @@ def fact_value(env, fact, value):
|
||||
:param value: Filter facts whose value is equal to this
|
||||
:type value: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if env == '*':
|
||||
query = None
|
||||
else:
|
||||
query = '["=", "environment", "{0}"]'.format(env)
|
||||
facts = get_or_abort(puppetdb.facts,
|
||||
name=fact,
|
||||
value=value,
|
||||
query='["=", "environment", "{0}"]'.format(env))
|
||||
query=query)
|
||||
localfacts = [f for f in yield_or_stop(facts)]
|
||||
return render_template(
|
||||
'fact.html',
|
||||
@@ -614,7 +729,7 @@ def fact_value(env, fact, value):
|
||||
current_env=env)
|
||||
|
||||
|
||||
@app.route('/query', methods=('GET', 'POST'), defaults={'env': 'production'})
|
||||
@app.route('/query', methods=('GET', 'POST'), defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/query', methods=('GET', 'POST'))
|
||||
def query(env):
|
||||
"""Allows to execute raw, user created querries against PuppetDB. This is
|
||||
@@ -627,9 +742,10 @@ def query(env):
|
||||
select field in the environment block
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
|
||||
if app.config['ENABLE_QUERY']:
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
form = QueryForm()
|
||||
if form.validate_on_submit():
|
||||
if form.query.data[0] == '[':
|
||||
@@ -654,7 +770,7 @@ def query(env):
|
||||
abort(403)
|
||||
|
||||
|
||||
@app.route('/metrics', defaults={'env': 'production'})
|
||||
@app.route('/metrics', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/metrics')
|
||||
def metrics(env):
|
||||
"""Lists all available metrics that PuppetDB is aware of.
|
||||
@@ -663,7 +779,8 @@ def metrics(env):
|
||||
for the environments template block
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
metrics = get_or_abort(puppetdb._query, 'mbean')
|
||||
for key, value in metrics.items():
|
||||
@@ -674,7 +791,7 @@ def metrics(env):
|
||||
current_env=env)
|
||||
|
||||
|
||||
@app.route('/metric/<metric>', defaults={'env': 'production'})
|
||||
@app.route('/metric/<metric>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/metric/<metric>')
|
||||
def metric(env, metric):
|
||||
"""Lists all information about the metric of the given name.
|
||||
@@ -683,7 +800,8 @@ def metric(env, metric):
|
||||
for the environments template block
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
name = unquote(metric)
|
||||
metric = puppetdb.metric(metric)
|
||||
@@ -694,7 +812,7 @@ def metric(env, metric):
|
||||
envs=envs,
|
||||
current_env=env)
|
||||
|
||||
@app.route('/catalogs', defaults={'env': 'production'})
|
||||
@app.route('/catalogs', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/catalogs')
|
||||
def catalogs(env):
|
||||
"""Lists all nodes with a compiled catalog.
|
||||
@@ -702,15 +820,20 @@ def catalogs(env):
|
||||
:param env: Find the nodes with this catalog_environment value
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if app.config['ENABLE_CATALOG']:
|
||||
nodenames = []
|
||||
catalog_list = []
|
||||
nodes = get_or_abort(puppetdb.nodes,
|
||||
query='["and",' \
|
||||
if env == '*':
|
||||
query = '["null?", "catalog_timestamp", false]]'
|
||||
else:
|
||||
query = '["and",' \
|
||||
'["=", "catalog_environment", "{0}"],' \
|
||||
'["null?", "catalog_timestamp", false]]'.format(env),
|
||||
nodes = get_or_abort(puppetdb.nodes,
|
||||
query=query,
|
||||
with_status=False,
|
||||
order_by='[{"field": "certname", "order": "asc"}]')
|
||||
nodes, temp = tee(nodes)
|
||||
@@ -745,7 +868,7 @@ def catalogs(env):
|
||||
log.warn('Access to catalog interface disabled by administrator')
|
||||
abort(403)
|
||||
|
||||
@app.route('/catalog/<node_name>', defaults={'env': 'production'})
|
||||
@app.route('/catalog/<node_name>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/catalog/<node_name>')
|
||||
def catalog_node(env, node_name):
|
||||
"""Fetches from PuppetDB the compiled catalog of a given node.
|
||||
@@ -753,7 +876,8 @@ def catalog_node(env, node_name):
|
||||
:param env: Find the catalog with this environment value
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if app.config['ENABLE_CATALOG']:
|
||||
catalog = get_or_abort(puppetdb.catalog,
|
||||
@@ -766,7 +890,7 @@ def catalog_node(env, node_name):
|
||||
log.warn('Access to catalog interface disabled by administrator')
|
||||
abort(403)
|
||||
|
||||
@app.route('/catalog/submit', methods=['POST'], defaults={'env': 'production'})
|
||||
@app.route('/catalog/submit', methods=['POST'], defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/catalog/submit', methods=['POST'])
|
||||
def catalog_submit(env):
|
||||
"""Receives the submitted form data from the catalogs page and directs
|
||||
@@ -778,7 +902,8 @@ def catalog_submit(env):
|
||||
catalogs page with the right environment.
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if app.config['ENABLE_CATALOG']:
|
||||
form = CatalogForm(request.form)
|
||||
@@ -797,7 +922,7 @@ def catalog_submit(env):
|
||||
log.warn('Access to catalog interface disabled by administrator')
|
||||
abort(403)
|
||||
|
||||
@app.route('/catalogs/compare/<compare>...<against>', defaults={'env': 'production'})
|
||||
@app.route('/catalogs/compare/<compare>...<against>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
|
||||
@app.route('/<env>/catalogs/compare/<compare>...<against>')
|
||||
def catalog_compare(env, compare, against):
|
||||
"""Compares the catalog of one node, parameter compare, with that of
|
||||
@@ -806,7 +931,8 @@ def catalog_compare(env, compare, against):
|
||||
:param env: Ensure that the 2 catalogs are in the same environment
|
||||
:type env: :obj:`string`
|
||||
"""
|
||||
check_env(env)
|
||||
envs = environments()
|
||||
check_env(env, envs)
|
||||
|
||||
if app.config['ENABLE_CATALOG']:
|
||||
compare_cat = get_or_abort(puppetdb.catalog,
|
||||
|
||||
@@ -6,6 +6,7 @@ PUPPETDB_SSL_VERIFY = True
|
||||
PUPPETDB_KEY = None
|
||||
PUPPETDB_CERT = None
|
||||
PUPPETDB_TIMEOUT = 20
|
||||
DEFAULT_ENVIRONMENT = 'production'
|
||||
SECRET_KEY = os.urandom(24)
|
||||
DEV_LISTEN_HOST = '127.0.0.1'
|
||||
DEV_LISTEN_PORT = 5000
|
||||
@@ -18,6 +19,7 @@ REPORTS_COUNT = 10
|
||||
OFFLINE_MODE = False
|
||||
ENABLE_CATALOG = False
|
||||
GRAPH_FACTS = ['architecture',
|
||||
'clientversion',
|
||||
'domain',
|
||||
'lsbcodename',
|
||||
'lsbdistcodename',
|
||||
@@ -34,3 +36,4 @@ INVENTORY_FACTS = [ ('Hostname', 'fqdn' ),
|
||||
('Architecture', 'hardwaremodel' ),
|
||||
('Kernel Version', 'kernelrelease' ),
|
||||
('Puppet Version', 'puppetversion' ), ]
|
||||
REFRESH_RATE = 30
|
||||
|
||||
@@ -36,6 +36,55 @@ th.tablesorter-headerDesc::after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ui.label.status {
|
||||
color: white;
|
||||
text-shadow: 0 0 1px;
|
||||
}
|
||||
|
||||
.ui.header.failed, .ui.line.failed {
|
||||
color: #AA4643;
|
||||
}
|
||||
|
||||
.ui.label.failed {
|
||||
background-color: #AA4643;
|
||||
}
|
||||
|
||||
.ui.header.changed, .ui.line.changed {
|
||||
color: #4572A7;
|
||||
}
|
||||
|
||||
.ui.label.changed {
|
||||
background-color: #4572A7;
|
||||
}
|
||||
|
||||
.ui.header.unreported {
|
||||
color: #3D96AE;
|
||||
}
|
||||
|
||||
.ui.label.unreported {
|
||||
background-color: #3D96AE;
|
||||
}
|
||||
|
||||
.ui.header.noop {
|
||||
color: #DB843D;
|
||||
}
|
||||
|
||||
.ui.label.noop {
|
||||
background-color: #DB843D;
|
||||
}
|
||||
|
||||
.ui.label.unchanged {
|
||||
background-color: #89A54E;
|
||||
}
|
||||
|
||||
.ui.line.skipped {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.ui.label.skipped {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.count {
|
||||
width: 14%;
|
||||
text-align: center;
|
||||
|
||||
@@ -150,13 +150,15 @@
|
||||
{% macro status_counts(caller, status, node_name, events, current_env, unreported_time=False, report_hash=False) -%}
|
||||
<a class="ui small status label
|
||||
{% if status == 'failed' -%}
|
||||
red
|
||||
failed
|
||||
{% elif status == 'changed' -%}
|
||||
green
|
||||
changed
|
||||
{% elif status == 'unreported' -%}
|
||||
black
|
||||
unreported
|
||||
{% elif status == 'noop' -%}
|
||||
blue
|
||||
noop
|
||||
{% elif status == 'unchanged' -%}
|
||||
unchanged
|
||||
{% endif -%}
|
||||
" href="
|
||||
{% if report_hash -%}
|
||||
@@ -170,9 +172,9 @@
|
||||
{% if status == 'unreported' %}
|
||||
<span class="ui small label status"> {{ unreported_time }} </span>
|
||||
{% else %}
|
||||
{% if events['failures'] %}<span class="ui small count label red">{{events['failures']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
||||
{% if events['successes'] %}<span class="ui small count label green">{{events['successes']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
||||
{% if events['skips'] %}<span class="ui small count label orange">{{events['skips']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
||||
{% if events['failures'] %}<span class="ui small count label failed">{{events['failures']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
||||
{% if events['successes'] %}<span class="ui small count label changed">{{events['successes']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
||||
{% if events['skips'] %}<span class="ui small count label skipped">{{events['skips']}}</span>{% else %}<span class="ui small count label">0</span>{% endif%}
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
{% macro render_pagination(pagination) -%}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<th>Hostname</th>
|
||||
<th>Version</th>
|
||||
<th>Transaction UUID</th>
|
||||
<th>Code ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -17,6 +18,7 @@
|
||||
<td><a href="{{url_for('node', env=current_env, node_name=catalog.node)}}">{{catalog.node}}</a></td>
|
||||
<td>{{catalog.version}}</td>
|
||||
<td>{{catalog.transaction_uuid}}</td>
|
||||
<td>{{catalog.code_id}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="ui basic table compact catalog">
|
||||
<table class="ui fixed table compact catalog">
|
||||
<thead>
|
||||
<tr><th>Resources</th></tr>
|
||||
</thead>
|
||||
@@ -29,7 +29,7 @@
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table class="ui basic table compact catalog">
|
||||
<table class="ui fixed table compact catalog">
|
||||
<thead>
|
||||
<tr><th>Resources</th></tr>
|
||||
</thead>
|
||||
@@ -45,7 +45,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="ui basic table compact catalog">
|
||||
<table class="ui fixed table compact catalog">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Edges</th>
|
||||
@@ -65,7 +65,7 @@
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table class="ui basic table compact catalog">
|
||||
<table class="ui fixed table compact catalog">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Edge</th>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% import '_macros.html' as macros %}
|
||||
{% block content %}
|
||||
{% if config.REFRESH_RATE > 0 %}
|
||||
<meta http-equiv="refresh" content="{{config.REFRESH_RATE}}">
|
||||
{% endif %}
|
||||
<div class="ui vertical grid">
|
||||
<div class="four column row">
|
||||
<div class="column">
|
||||
<a href="{{url_for('nodes', env=current_env, status='failed')}}">
|
||||
<h1 class="ui red header no-margin-bottom">
|
||||
<h1 class="ui failed header no-margin-bottom">
|
||||
{{stats['failed']}}
|
||||
<small>{% if stats['failed']== 1 %} node {% else %} nodes {% endif %}</small>
|
||||
</h1>
|
||||
@@ -14,7 +17,7 @@
|
||||
</div>
|
||||
<div class="column">
|
||||
<a href="{{url_for('nodes', env=current_env, status='noop')}}">
|
||||
<h1 class="ui header purple no-margin-bottom">
|
||||
<h1 class="ui header noop no-margin-bottom">
|
||||
{{stats['noop']}}
|
||||
<small>{% if stats['noop']== 1 %} node {% else %} nodes {% endif %}</small>
|
||||
</h1>
|
||||
@@ -23,7 +26,7 @@
|
||||
</div>
|
||||
<div class="column">
|
||||
<a href="{{url_for('nodes', env=current_env, status='changed')}}">
|
||||
<h1 class="ui header green no-margin-bottom">
|
||||
<h1 class="ui header changed no-margin-bottom">
|
||||
{{stats['changed']}}
|
||||
<small>{% if stats['changed']== 1 %} node {% else %} nodes {% endif %}</small>
|
||||
</h1>
|
||||
@@ -32,7 +35,7 @@
|
||||
</div>
|
||||
<div class="column">
|
||||
<a href="{{url_for('nodes', env=current_env, status='unreported')}}">
|
||||
<h1 class="ui header black no-margin-bottom">
|
||||
<h1 class="ui header unreported no-margin-bottom">
|
||||
{{ stats['unreported'] }}
|
||||
<small>{% if stats['unreported']== 1 %} node {% else %} nodes {% endif %}</small>
|
||||
</h1>
|
||||
@@ -76,14 +79,22 @@
|
||||
{% if node.status != 'unchanged' %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if node.latest_report_hash %}
|
||||
{{macros.status_counts(status=node.status, node_name=node.name, events=node.events, unreported_time=node.unreported_time, current_env=current_env, report_hash=node.latest_report_hash)}}
|
||||
{% else %}
|
||||
{{macros.status_counts(status=node.status, node_name=node.name, events=node.events, unreported_time=node.unreported_time, current_env=current_env)}}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{url_for('node', env=current_env, node_name=node.name)}}">{{ node.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if node.report_timestamp %}
|
||||
{% if node.latest_report_hash %}
|
||||
<a href="{{url_for('report', env=current_env, node_name=node.name, report_id=node.latest_report_hash)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
|
||||
{% else %}
|
||||
<a href="{{url_for('report_latest', env=current_env, node_name=node.name)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<i class="large ban circle icon"></i>
|
||||
{% endif %}
|
||||
|
||||
@@ -44,12 +44,13 @@
|
||||
Environments
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="menu">
|
||||
<a class="item {% if '*' == current_env %}active{% endif %}" href="{{url_for_environments('*')}}">All environments</a>
|
||||
{% for env in envs %}
|
||||
<a class="item {% if env == current_env %}active{% endif %}" href="{{url_for_environments(env)}}">{{env}}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item right"><a href="https://github.com/puppet-community/puppetboard" target="_blank">v0.1.0</a></div>
|
||||
<div class="item right"><a href="https://github.com/puppet-community/puppetboard" target="_blank">v0.1.2</a></div>
|
||||
</div>
|
||||
<div class="ui grid padding-bottom">
|
||||
<div class="one wide column"></div>
|
||||
|
||||
@@ -18,13 +18,21 @@
|
||||
{% for node in nodes %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if node.latest_report_hash %}
|
||||
{{macros.status_counts(status=node.status, node_name=node.name, events=node.events, unreported_time=node.unreported_time, current_env=current_env, report_hash=node.latest_report_hash)}}
|
||||
{% else %}
|
||||
{{macros.status_counts(status=node.status, node_name=node.name, events=node.events, unreported_time=node.unreported_time, current_env=current_env)}}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><a href="{{url_for('node', env=current_env, node_name=node.name)}}">{{node.name}}</a></td>
|
||||
<td><a rel="utctimestamp" href="{{url_for('catalog_node', env=current_env, node_name=node.name)}}">{{node.catalog_timestamp}}</a></td>
|
||||
<td>
|
||||
{% if node.report_timestamp %}
|
||||
{% if node.latest_report_hash %}
|
||||
<a href="{{url_for('report', env=current_env, node_name=node.name, report_id=node.latest_report_hash)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
|
||||
{% else %}
|
||||
<a href="{{url_for('report_latest', env=current_env, node_name=node.name)}}" rel='utctimestamp'>{{ node.report_timestamp }}</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<i class="large ban circle icon"></i>
|
||||
{% endif %}
|
||||
|
||||
@@ -39,9 +39,11 @@
|
||||
<tbody>
|
||||
{% for event in events %}
|
||||
{% if not event.failed and event.item['old'] != event.item['new'] %}
|
||||
<tr id='event-{{loop.index}}' class='positive'>
|
||||
<tr id='event-{{loop.index}}' class='ui line changed'>
|
||||
{% elif event.failed %}
|
||||
<tr id='event-{{loop.index}}' class='error'>
|
||||
<tr id='event-{{loop.index}}' class='ui line failed'>
|
||||
{% else %}
|
||||
<tr id='event-{{loop.index}}' class='ui line {{event.status}}'>
|
||||
{% endif %}
|
||||
<td>{{event.item['type']}}[{{event.item['title']}}]</td>
|
||||
<td>{{event.status}}</td>
|
||||
|
||||
@@ -5,5 +5,5 @@ MarkupSafe==0.19
|
||||
WTForms==1.0.5
|
||||
Werkzeug==0.9.4
|
||||
itsdangerous==0.23
|
||||
pypuppetdb==0.2.0
|
||||
pypuppetdb==0.2.1
|
||||
requests==2.2.1
|
||||
|
||||
4
setup.py
4
setup.py
@@ -9,7 +9,7 @@ if sys.argv[-1] == 'publish':
|
||||
os.system('python setup.py sdist upload')
|
||||
sys.exit()
|
||||
|
||||
VERSION = "0.1.0"
|
||||
VERSION = "0.1.2"
|
||||
|
||||
with codecs.open('README.rst', encoding='utf-8') as f:
|
||||
README = f.read()
|
||||
@@ -32,7 +32,7 @@ setup(
|
||||
"Flask >= 0.10.1",
|
||||
"Flask-WTF >= 0.9.4, <= 0.9.5",
|
||||
"WTForms < 2.0",
|
||||
"pypuppetdb >= 0.2.0, < 0.3.0",
|
||||
"pypuppetdb >= 0.2.1, < 0.3.0",
|
||||
],
|
||||
keywords="puppet puppetdb puppetboard",
|
||||
classifiers=[
|
||||
|
||||
Reference in New Issue
Block a user