Merge pull request #135 from stoyansbg/master

Added inventory page along with a navbar link.
This commit is contained in:
Spencer Krum
2015-06-09 16:24:19 -07:00
5 changed files with 94 additions and 0 deletions

View File

@@ -180,6 +180,64 @@ def nodes():
stream_template('nodes.html', nodes=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/<node_name>') @app.route('/node/<node_name>')
def node(node_name): def node(node_name):
"""Display a dashboard for a node showing as much data as we have on that """Display a dashboard for a node showing as much data as we have on that

View File

@@ -27,3 +27,9 @@ GRAPH_FACTS = ['architecture',
'osfamily', 'osfamily',
'puppetversion', 'puppetversion',
'processorcount'] 'processorcount']
INVENTORY_FACTS = [ ('Hostname', 'fqdn' ),
('IP Address', 'ipaddress' ),
('OS', 'lsbdistdescription'),
('Architecture', 'hardwaremodel' ),
('Kernel Version', 'kernelrelease' ),
('Puppet Version', 'puppetversion' ), ]

View File

@@ -32,6 +32,10 @@
sortList: [[1, 0]] sortList: [[1, 0]]
}); });
$('.inventory').tablesorter({
sortList: [[0, 0]]
});
$('.facts').tablesorter({ $('.facts').tablesorter({
sortList: [[0, 0]] sortList: [[0, 0]]
}); });

View File

@@ -0,0 +1,25 @@
{% 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 compact basic table inventory'>
<thead>
<tr>
{% for description in fact_desc %}
<th>{{description}}</th>
{% endfor %}
</tr>
</thead>
<tbody class="searchable">
{% for nodename in nodedata %}
<tr>
{% for item in nodedata[nodename] %}
<td>{{item}}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}

View File

@@ -33,6 +33,7 @@
('facts', 'Facts'), ('facts', 'Facts'),
('reports', 'Reports'), ('reports', 'Reports'),
('metrics', 'Metrics'), ('metrics', 'Metrics'),
('inventory', 'Inventory'),
('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 %}