diff --git a/puppetboard/app.py b/puppetboard/app.py index 84e9b33..8a54fac 100644 --- a/puppetboard/app.py +++ b/puppetboard/app.py @@ -180,6 +180,64 @@ def nodes(): stream_template('nodes.html', nodes=nodes))) +@app.route('/inventory') +def inventory(): + """Fetch all (active) nodes from PuppetDB and stream a table displaying + those nodes along with a set of facts about them. + + Downside of the streaming aproach is that since we've already sent our + headers we can't abort the request if we detect an error. Because of this + we'll end up with an empty table instead because of how yield_or_stop + works. Once pagination is in place we can change this but we'll need to + provide a search feature instead. + """ + + fact_desc = [] # a list of fact descriptions to go + # in the table header + fact_names = [] # a list of inventory fact names + factvalues = {} # values of the facts for all the nodes + # indexed by node name and fact name + nodedata = {} # a dictionary containing list of inventoried + # facts indexed by node name + nodelist = set() # a set of node names + + # get all the facts from PuppetDB + facts = puppetdb.facts() + + # load the list of items/facts we want in our inventory + try: + inv_facts = app.config['INVENTORY_FACTS'] + except KeyError: + inv_facts = [ ('Hostname' ,'fqdn' ), + ('IP Address' ,'ipaddress' ), + ('OS' ,'lsbdistdescription'), + ('Architecture' ,'hardwaremodel' ), + ('Kernel Version','kernelrelease' ) ] + + # generate a list of descriptions and a list of fact names + # from the list of tuples inv_facts. + for description,name in inv_facts: + fact_desc.append(description) + fact_names.append(name) + + # convert the json in easy to access data structure + for fact in facts: + factvalues[fact.node,fact.name] = fact.value + nodelist.add(fact.node) + + # generate the per-host data + for node in nodelist: + nodedata[node] = [] + for fact_name in fact_names: + try: + nodedata[node].append(factvalues[node,fact_name]) + except KeyError: + nodedata[node].append("undef") + + return Response(stream_with_context( + stream_template('inventory.html', nodedata=nodedata, fact_desc=fact_desc))) + + @app.route('/node/') def node(node_name): """Display a dashboard for a node showing as much data as we have on that diff --git a/puppetboard/default_settings.py b/puppetboard/default_settings.py index d4bd3eb..2dd76b6 100644 --- a/puppetboard/default_settings.py +++ b/puppetboard/default_settings.py @@ -27,3 +27,9 @@ GRAPH_FACTS = ['architecture', 'osfamily', 'puppetversion', 'processorcount'] +INVENTORY_FACTS = [ ('Hostname', 'fqdn' ), + ('IP Address', 'ipaddress' ), + ('OS', 'lsbdistdescription'), + ('Architecture', 'hardwaremodel' ), + ('Kernel Version', 'kernelrelease' ), + ('Puppet Version', 'puppetversion' ), ] diff --git a/puppetboard/static/js/tables.js b/puppetboard/static/js/tables.js index ac864dd..3c945f7 100644 --- a/puppetboard/static/js/tables.js +++ b/puppetboard/static/js/tables.js @@ -32,6 +32,10 @@ sortList: [[1, 0]] }); + $('.inventory').tablesorter({ + sortList: [[0, 0]] + }); + $('.facts').tablesorter({ sortList: [[0, 0]] }); diff --git a/puppetboard/templates/inventory.html b/puppetboard/templates/inventory.html new file mode 100644 index 0000000..e3e6567 --- /dev/null +++ b/puppetboard/templates/inventory.html @@ -0,0 +1,25 @@ +{% extends 'layout.html' %} +{% block content %} +
+ +
+ + + + {% for description in fact_desc %} + + {% endfor %} + + + + {% for nodename in nodedata %} + + + {% for item in nodedata[nodename] %} + + {% endfor %} + + {% endfor %} + +
{{description}}
{{item}}
+{% endblock content %} diff --git a/puppetboard/templates/layout.html b/puppetboard/templates/layout.html index 803a45c..910bab3 100644 --- a/puppetboard/templates/layout.html +++ b/puppetboard/templates/layout.html @@ -33,6 +33,7 @@ ('facts', 'Facts'), ('reports', 'Reports'), ('metrics', 'Metrics'), + ('inventory', 'Inventory'), ('query', 'Query'), ] %}