diff --git a/puppetboard/app.py b/puppetboard/app.py index 7662f10..de0d54a 100644 --- a/puppetboard/app.py +++ b/puppetboard/app.py @@ -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/') 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/...') +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) diff --git a/puppetboard/forms.py b/puppetboard/forms.py index f4667c3..5be3b1f 100644 --- a/puppetboard/forms.py +++ b/puppetboard/forms.py @@ -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') diff --git a/puppetboard/templates/catalog_compare.html b/puppetboard/templates/catalog_compare.html new file mode 100644 index 0000000..705210a --- /dev/null +++ b/puppetboard/templates/catalog_compare.html @@ -0,0 +1,87 @@ +{% extends 'layout.html' %} +{% block content %} + + + + + + + + + + + + + + + + + + + +

Comparing

Against

{{compare.node}}{{against.node}}
+ + + + + + {% for resource in compare.get_resources() %} + + + + {% endfor %} + +
Resources
{{resource.type_}}[{{resource.name}}]
+
+ + + + + + {% for resource in against.get_resources() %} + + + + {% endfor %} + +
Resources
{{resource.type_}}[{{resource.name}}]
+
+ + + + + + + + + + {% for edge in compare.get_edges() %} + + + + + + {% endfor %} + +
Edges->Target
{{edge.source}}{{edge.relationship}}{{edge.target}}
+
+ + + + + + + + + + {% for edge in against.get_edges() %} + + + + + + {% endfor %} + +
Edge->Target
{{edge.source}}{{edge.relationship}}{{edge.target}}
+
+{% endblock content %} diff --git a/puppetboard/templates/catalogs.html b/puppetboard/templates/catalogs.html new file mode 100644 index 0000000..52dca2b --- /dev/null +++ b/puppetboard/templates/catalogs.html @@ -0,0 +1,40 @@ +{% extends 'layout.html' %} +{% import '_macros.html' as macros %} +{% block content %} +
+ +
+ + + + + + + + + + + {% for node in nodes %} + + + + + + + {% endfor %} + +
HostnameCompile TimeCompare With
{{node.name}}{{node.catalog_timestamp}} + {% if node.form %} +
+
+ {{node.form.csrf_token}} +
+ {{node.form.compare}} + {{node.form.against}} + +
+
+
+ {% endif %} +
+{% endblock content %} diff --git a/puppetboard/templates/layout.html b/puppetboard/templates/layout.html index 910bab3..6e4a3e4 100644 --- a/puppetboard/templates/layout.html +++ b/puppetboard/templates/layout.html @@ -34,6 +34,7 @@ ('reports', 'Reports'), ('metrics', 'Metrics'), ('inventory', 'Inventory'), + ('catalogs', 'Catalogs'), ('query', 'Query'), ] %}