puppetboard: Adding a more intuitive catalog view

A new endpoint in the header, Catalogs, takes the user to a page with a node
table similar to that in the nodes page. This table shows the node with a
link to the node page, the catalog timestamp with a link to the catalog
page and a small form with a select field to be used to compare the
catalog of this row's node with that of another node.
This commit is contained in:
Corey Hammerton
2015-09-23 11:16:20 -04:00
parent bce33aee0f
commit a3473abf61
5 changed files with 217 additions and 2 deletions

View File

@@ -18,7 +18,7 @@ from flask_wtf.csrf import CsrfProtect
from pypuppetdb import connect
from puppetboard.forms import QueryForm
from puppetboard.forms import (CatalogForm, QueryForm)
from puppetboard.utils import (
get_or_abort, yield_or_stop,
limit_reports, jsonprint
@@ -404,6 +404,45 @@ def metric(metric):
name=name,
metric=sorted(metric.items()))
@app.route('/catalogs')
def catalogs():
if app.config['ENABLE_CATALOG']:
nodenames = []
catalog_list = []
nodes = get_or_abort(puppetdb.nodes,
query='["null?", "catalog_timestamp", false]',
with_status=False,
order_by='[{"field": "certname", "order": "asc"}]')
nodes, temp = tee(nodes)
for node in temp:
nodenames.append(node.name)
for node in nodes:
table_row = {
'name': node.name,
'catalog_timestamp': node.catalog_timestamp
}
if len(nodenames) > 1:
form = CatalogForm()
form.compare.data = node.name
form.against.choices = [(x, x) for x in nodenames
if x != node.name]
table_row['form'] = form
else:
table_row['form'] = None
catalog_list.append(table_row)
return render_template(
'catalogs.html',
nodes=catalog_list)
else:
log.warn('Access to catalogs endpoint disabled by administrator')
abort(403)
@app.route('/catalog/<node_name>')
def catalog_node(node_name):
"""Fetches from PuppetDB the compiled catalog of a given node."""
@@ -413,3 +452,43 @@ def catalog_node(node_name):
else:
log.warn('Access to catalog interface disabled by administrator')
abort(403)
@app.route('/catalog/submit', methods=['POST'])
def catalog_submit():
"""Receives the submitted form data from the catalogs page and directs
the users to the comparison page. Directs users back to the catalogs
page if no form submission data is found.
"""
if app.config['ENABLE_CATALOG']:
form = CatalogForm(request.form)
form.against.choices = [(form.against.data, form.against.data)]
if form.validate_on_submit():
compare = form.compare.data
against = form.against.data
return redirect(
url_for('catalog_compare',
compare=compare,
against=against))
return redirect(url_for('catalogs'))
else:
log.warn('Access to catalog interface disabled by administrator')
abort(403)
@app.route('/catalogs/compare/<compare>...<against>')
def catalog_compare(compare, against):
"""Compares the catalog of one node, parameter compare, with that of
with that of another node, parameter against.
"""
if app.config['ENABLE_CATALOG']:
compare_cat = get_or_abort(puppetdb.catalog,
node=compare)
against_cat = get_or_abort(puppetdb.catalog,
node=against)
return render_template('catalog_compare.html',
compare=compare_cat,
against=against_cat)
else:
log.warn('Access to catalog interface disabled by administrator')
abort(403)

View File

@@ -2,7 +2,10 @@ from __future__ import unicode_literals
from __future__ import absolute_import
from flask.ext.wtf import Form
from wtforms import RadioField, TextAreaField, validators
from wtforms import (
HiddenField, RadioField, SelectField,
TextAreaField, validators
)
class QueryForm(Form):
@@ -18,3 +21,8 @@ class QueryForm(Form):
('reports', 'Reports'),
('events', 'Events'),
])
class CatalogForm(Form):
"""The form used to compare the catalogs of different nodes."""
compare = HiddenField('compare')
against = SelectField(u'against')

View File

@@ -0,0 +1,87 @@
{% extends 'layout.html' %}
{% block content %}
<table class="ui basic table">
<tbody>
<tr>
<th><h1>Comparing</h1></th>
<th><h1>Against</h1></th>
</tr>
<tr>
<td>{{compare.node}}</td>
<td>{{against.node}}</td>
</tr>
<tr>
<td>
<table class="ui basic table compact catalog">
<thead>
<tr><th>Resources</th></tr>
</thead>
<tbody>
{% for resource in compare.get_resources() %}
<tr>
<td>{{resource.type_}}[{{resource.name}}]</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
<td>
<table class="ui basic table compact catalog">
<thead>
<tr><th>Resources</th></tr>
</thead>
<tbody>
{% for resource in against.get_resources() %}
<tr>
<td>{{resource.type_}}[{{resource.name}}]</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
</tr>
<tr>
<td>
<table class="ui basic table compact catalog">
<thead>
<tr>
<th>Edges</th>
<th>-&gt;</th>
<th>Target</th>
</tr>
</thead>
<tbody>
{% for edge in compare.get_edges() %}
<tr>
<td>{{edge.source}}</td>
<td>{{edge.relationship}}</td>
<td>{{edge.target}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
<td>
<table class="ui basic table compact catalog">
<thead>
<tr>
<th>Edge</th>
<th>-&gt;</th>
<th>Target</th>
</tr>
</thead>
<tbody>
{% for edge in against.get_edges() %}
<tr>
<td>{{edge.source}}</td>
<td>{{edge.relationship}}</td>
<td>{{edge.target}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
{% endblock content %}

View File

@@ -0,0 +1,40 @@
{% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% block content %}
<div class="ui fluid icon input hide" style="margin-bottom:20px">
<input autofocus="autofocus" class="filter-table" placeholder="Type here to filter...">
</div>
<table class='ui compact basic table nodes'>
<thead>
<tr>
<th></th>
<th>Hostname</th>
<th>Compile Time</th>
<th>Compare With</th>
</tr>
</thead>
<tbody class="searchable">
{% for node in nodes %}
<tr>
<td></td>
<td><a href="{{url_for('node', node_name=node.name)}}">{{node.name}}</a></td>
<td><a rel="utctimestamp" href="{{url_for('catalog_node', node_name=node.name)}}">{{node.catalog_timestamp}}</a></td>
<td>
{% if node.form %}
<div class="ui form">
<form method="POST" action="{{url_for('catalog_submit')}}">
{{node.form.csrf_token}}
<div class="field inline">
{{node.form.compare}}
{{node.form.against}}
<input type="submit" class="ui submit button" style="height:auto;" value="Compare"/>
</div>
</form>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}

View File

@@ -34,6 +34,7 @@
('reports', 'Reports'),
('metrics', 'Metrics'),
('inventory', 'Inventory'),
('catalogs', 'Catalogs'),
('query', 'Query'),
] %}
<a {% if endpoint == request.endpoint %} class="active item" {% else %} class="item" {% endif %}