Merge pull request #161 from corey-hammerton/catalog
puppetboard: Adding a more intuitive catalog view
This commit is contained in:
@@ -18,7 +18,7 @@ from flask_wtf.csrf import CsrfProtect
|
|||||||
|
|
||||||
from pypuppetdb import connect
|
from pypuppetdb import connect
|
||||||
|
|
||||||
from puppetboard.forms import QueryForm
|
from puppetboard.forms import (CatalogForm, QueryForm)
|
||||||
from puppetboard.utils import (
|
from puppetboard.utils import (
|
||||||
get_or_abort, yield_or_stop,
|
get_or_abort, yield_or_stop,
|
||||||
limit_reports, jsonprint
|
limit_reports, jsonprint
|
||||||
@@ -407,6 +407,45 @@ def metric(metric):
|
|||||||
name=name,
|
name=name,
|
||||||
metric=sorted(metric.items()))
|
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>')
|
@app.route('/catalog/<node_name>')
|
||||||
def catalog_node(node_name):
|
def catalog_node(node_name):
|
||||||
"""Fetches from PuppetDB the compiled catalog of a given node."""
|
"""Fetches from PuppetDB the compiled catalog of a given node."""
|
||||||
@@ -416,3 +455,43 @@ def catalog_node(node_name):
|
|||||||
else:
|
else:
|
||||||
log.warn('Access to catalog interface disabled by administrator')
|
log.warn('Access to catalog interface disabled by administrator')
|
||||||
abort(403)
|
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)
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ from __future__ import unicode_literals
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from flask.ext.wtf import Form
|
from flask.ext.wtf import Form
|
||||||
from wtforms import RadioField, TextAreaField, validators
|
from wtforms import (
|
||||||
|
HiddenField, RadioField, SelectField,
|
||||||
|
TextAreaField, validators
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class QueryForm(Form):
|
class QueryForm(Form):
|
||||||
@@ -18,3 +21,8 @@ class QueryForm(Form):
|
|||||||
('reports', 'Reports'),
|
('reports', 'Reports'),
|
||||||
('events', 'Events'),
|
('events', 'Events'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
class CatalogForm(Form):
|
||||||
|
"""The form used to compare the catalogs of different nodes."""
|
||||||
|
compare = HiddenField('compare')
|
||||||
|
against = SelectField('against')
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<th>Target</th>
|
<th>Target</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class='searchable'>
|
||||||
{% for edge in catalog.get_edges() %}
|
{% for edge in catalog.get_edges() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{edge.source}}</td>
|
<td>{{edge.source}}</td>
|
||||||
|
|||||||
90
puppetboard/templates/catalog_compare.html
Normal file
90
puppetboard/templates/catalog_compare.html
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% 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 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 class='searchable'>
|
||||||
|
{% 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 class='searchable'>
|
||||||
|
{% 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>-></th>
|
||||||
|
<th>Target</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class='searchable'>
|
||||||
|
{% 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>-></th>
|
||||||
|
<th>Target</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class='searchable'>
|
||||||
|
{% 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 %}
|
||||||
40
puppetboard/templates/catalogs.html
Normal file
40
puppetboard/templates/catalogs.html
Normal 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 %}
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
('reports', 'Reports'),
|
('reports', 'Reports'),
|
||||||
('metrics', 'Metrics'),
|
('metrics', 'Metrics'),
|
||||||
('inventory', 'Inventory'),
|
('inventory', 'Inventory'),
|
||||||
|
('catalogs', 'Catalogs'),
|
||||||
('query', 'Query'),
|
('query', 'Query'),
|
||||||
] %}
|
] %}
|
||||||
<a {% if endpoint == request.endpoint %} class="active item" {% else %} class="item" {% endif %}
|
<a {% if endpoint == request.endpoint %} class="active item" {% else %} class="item" {% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user