Added inventory page along with a navbar link.
Added inventory page showing a table similar to the "Nodes" one, but containing any customizable set of facts. A setting INVENTORY_FACTS has been added. It controls the list of facts to be displayed along with a description for the header of the table.
This commit is contained in:
@@ -178,6 +178,61 @@ 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:
|
||||
nodedata[node].append(factvalues[node,fact_name])
|
||||
|
||||
return Response(stream_with_context(
|
||||
stream_template('inventory.html', nodedata=nodedata, fact_desc=fact_desc)))
|
||||
|
||||
|
||||
@app.route('/node/<node_name>')
|
||||
def node(node_name):
|
||||
"""Display a dashboard for a node showing as much data as we have on that
|
||||
|
||||
@@ -23,3 +23,9 @@ GRAPH_FACTS = ['architecture',
|
||||
'osfamily',
|
||||
'puppetversion',
|
||||
'processorcount']
|
||||
INVENTORY_FACTS = [ ('Hostname', 'fqdn' ),
|
||||
('IP Address', 'ipaddress' ),
|
||||
('OS', 'lsbdistdescription'),
|
||||
('Architecture', 'hardwaremodel' ),
|
||||
('Kernel Version', 'kernelrelease' ),
|
||||
('Puppet Version', 'puppetversion' ), ]
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
sortList: [[1, 0]]
|
||||
});
|
||||
|
||||
$('.inventory').tablesorter({
|
||||
sortList: [[0, 0]]
|
||||
});
|
||||
|
||||
$('.facts').tablesorter({
|
||||
sortList: [[0, 0]]
|
||||
});
|
||||
|
||||
37
puppetboard/templates/inventory.html
Normal file
37
puppetboard/templates/inventory.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block content %}
|
||||
<div class="alert alert-info">
|
||||
PuppetDB currently only returns active nodes.
|
||||
</div>
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{category}}">
|
||||
{{message}}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="hide" style="margin-bottom:20px">
|
||||
<input autofocus="autofocus" style="width:100%" type="text" class="filter-table input-medium search-query" placeholder="Type here to filter">
|
||||
</div>
|
||||
<table class='inventory table table-striped table-condensed'>
|
||||
<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 %}
|
||||
@@ -33,6 +33,7 @@
|
||||
('facts', 'Facts'),
|
||||
('reports', 'Reports'),
|
||||
('metrics', 'Metrics'),
|
||||
('inventory', 'Inventory'),
|
||||
('query', 'Query'),
|
||||
] %}
|
||||
<a {% if endpoint == request.endpoint %} class="active item" {% else %} class="item" {% endif %}
|
||||
|
||||
Reference in New Issue
Block a user