diff --git a/puppetboard/app.py b/puppetboard/app.py index 526dd0b..2808ee5 100644 --- a/puppetboard/app.py +++ b/puppetboard/app.py @@ -8,7 +8,7 @@ import urllib from flask import ( Flask, render_template, abort, url_for, - Response, stream_with_context, + Response, stream_with_context, redirect, ) from pypuppetdb import connect @@ -71,8 +71,9 @@ def server_error(e): @app.route('/') def index(): - """This view generates the index page and displays a set of metrics fetched - from PuppetDB.""" + """This view generates the index page and displays a set of metrics and latest reports on + nodes 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, @@ -85,6 +86,8 @@ def index(): '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') + 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'], @@ -92,7 +95,13 @@ def index(): 'mean_failed_commands': mean_failed_commands['MeanRate'], 'mean_command_time': "{0:10.6f}".format(mean_command_time['MeanRate']), } - return render_template('index.html', metrics=metrics) + + latest_event_count = puppetdb._query('aggregate-event-counts', query='["=", "latest-report?", true]', summarize_by='certname') + latest_event_count['noopskip'] = latest_event_count['noops']+latest_event_count['skips'] + + latest_events = puppetdb._query('event-counts', query='["=", "latest-report?", true]', summarize_by='certname') + + return render_template('index.html', metrics=metrics, latest_event_count=latest_event_count, latest_events=latest_events) @app.route('/nodes') def nodes(): @@ -105,8 +114,19 @@ def nodes(): works. Once pagination is in place we can change this but we'll need to provide a search feature instead. """ + latest_events = puppetdb._query('event-counts', query='["=", "latest-report?", true]', summarize_by='certname') + nodes = [] + for node in yield_or_stop(puppetdb.nodes()): + # check if node name is contained in any of the event-counts (grouped by certname) + status = [s for s in latest_events if s['subject']['title'] == node.name] + if status: + node.status = status[0] + else: + node.status = None + nodes.append(node) + return Response(stream_with_context(stream_template('nodes.html', - nodes=yield_or_stop(puppetdb.nodes())))) + nodes=nodes))) @app.route('/node/') def node(node_name): @@ -146,10 +166,26 @@ def reports_node(node): return render_template('reports_node.html', reports=reports, nodename=node) +@app.route('/report/latest/') +def report_latest(node_name): + """Redirect to the latest report of a given node. This is a workaround + as long as PuppetDB can't filter reports for latest-report? field. This + feature has been requested: http://projects.puppetlabs.com/issues/21554 + """ + # TODO: use limit parameter in _query to get just one report + node = get_or_abort(puppetdb.node, node_name) + if app.config['PUPPETDB_API'] > 2: + reports = ten_reports(node.reports()) + else: + reports = iter([]) + report = list(yield_or_stop(reports))[0] + return redirect(url_for('report', node=node_name, report_id=report)) + @app.route('/report//') def report(node, report_id): """Displays a single report including all the events associated with that - report and their status.""" + report and their status. + """ if app.config['PUPPETDB_API'] > 2: reports = puppetdb.reports('["=", "certname", "{0}"]'.format(node)) else: diff --git a/puppetboard/static/css/puppetboard.css b/puppetboard/static/css/puppetboard.css index 7cd5098..f535a8c 100644 --- a/puppetboard/static/css/puppetboard.css +++ b/puppetboard/static/css/puppetboard.css @@ -52,3 +52,16 @@ th.headerSortDown:after { .table tbody tr.error>td { background-color: #f2dede; } +h1.error { + color: rgb(223, 46, 27); +} +h1.success { + color: #18BC9C; +} +h1.noop { + color:#aaa; +} +.numtag { + width:20px; + text-align:center; +} diff --git a/puppetboard/static/js/tables.js b/puppetboard/static/js/tables.js index 4628d49..7fb44b8 100644 --- a/puppetboard/static/js/tables.js +++ b/puppetboard/static/js/tables.js @@ -8,11 +8,11 @@ $('.nodes').tablesorter({ headers: { - 3: { + 4: { sorter: false } }, - sortList: [[0, 0]] + sortList: [[1, 0]] }); $('.facts').tablesorter({ diff --git a/puppetboard/templates/index.html b/puppetboard/templates/index.html index 4e41277..acf561c 100644 --- a/puppetboard/templates/index.html +++ b/puppetboard/templates/index.html @@ -1,11 +1,27 @@ {% extends 'layout.html' %} {% block row_fluid %} -
-
- We need something fancy here. -
-
+
+

Statistics

+
+
+
+
+
+

{{latest_event_count['failures']}} Failures

+ in the latest node reports +
+
+

{{latest_event_count['successes']}} Successes

+ in the latest node reports +
+
+

{{latest_event_count['noopskip']}} Noop/Skip

+ in the latest node reports +
+
+
+
@@ -34,5 +50,25 @@
+ +
+
+

Latest Reports with events

+
+ + + {% for event in latest_events %} + + + + + + + + {% endfor %} + +
{% if event['failures'] %}{{event['failures']}}{% endif%}{% if event['successes'] %}{{event['successes']}}{% endif%}{{event['subject']['title']}}Latest Report
+
+
{% endblock row_fluid %} diff --git a/puppetboard/templates/nodes.html b/puppetboard/templates/nodes.html index 9d483a7..bb879c8 100644 --- a/puppetboard/templates/nodes.html +++ b/puppetboard/templates/nodes.html @@ -18,6 +18,7 @@ + {% if config.PUPPETDB_API > 2 %} @@ -29,6 +30,16 @@ {% for node in nodes %} + {% if config.PUPPETDB_API > 2 %} diff --git a/requirements.txt b/requirements.txt index cab4027..f3975d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,5 @@ MarkupSafe==0.18 WTForms==1.0.4 Werkzeug==0.9.3 itsdangerous==0.22 -pypuppetdb==0.0.4 +git+git://github.com/juliushaertl/pypuppetdb.git@feature-v3-newapis#egg=pypuppetdb requests==1.2.3
Status Hostname Catalog compiled at
+ {% if node.status %} + {% if node.status['failures'] %}{{node.status['failures']}}{% endif %} + {% if node.status['successes'] %}{{node.status['successes']}}{% endif %} + {% if node.status['noops'] or node.status['skips'] %}{{node.status['skips']+node.status['noops']}}{% endif %} + {% else %} + 0 + {% endif %} + + {{node.name}} {{node.catalog_timestamp}}