Inventory revamp - client side

This commit is contained in:
redref
2017-02-04 20:03:09 +01:00
parent fd4051b619
commit 709d14e9a2
3 changed files with 82 additions and 48 deletions

View File

@@ -343,29 +343,11 @@ def nodes(env):
current_env=env))) current_env=env)))
@app.route('/inventory', defaults={'env': app.config['DEFAULT_ENVIRONMENT']}) def inventory_facts():
@app.route('/<env>/inventory') # a list of facts descriptions to go in table header
def inventory(env): headers = []
"""Fetch all (active) nodes from PuppetDB and stream a table displaying # a list of inventory fact names
those nodes along with a set of facts about them. fact_names = []
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.
:param env: Search for facts in this environment
:type env: :obj:`string`
"""
envs = environments()
check_env(env, envs)
headers = [] # a list of fact descriptions to go
# in the table header
fact_names = [] # a list of inventory fact names
fact_data = {} # a multidimensional dict for node and
# fact data
# load the list of items/facts we want in our inventory # load the list of items/facts we want in our inventory
try: try:
@@ -383,33 +365,65 @@ def inventory(env):
headers.append(desc) headers.append(desc)
fact_names.append(name) fact_names.append(name)
return headers, fact_names
@app.route('/inventory', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/inventory')
def inventory(env):
"""Fetch all (active) nodes from PuppetDB and stream a table displaying
those nodes along with a set of facts about them.
:param env: Search for facts in this environment
:type env: :obj:`string`
"""
envs = environments()
check_env(env, envs)
headers, fact_names = inventory_facts()
return render_template(
'inventory.html',
envs=envs,
current_env=env,
fact_headers=headers)
@app.route('/inventory/json',
defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/inventory/json')
def inventory_ajax(env):
"""Backend endpoint for inventory table"""
draw = int(request.args.get('draw', 0))
envs = environments()
check_env(env, envs)
headers, fact_names = inventory_facts()
query = AndOperator() query = AndOperator()
fact_query = OrOperator() fact_query = OrOperator()
fact_query.add([EqualsOperator("name", name) for name in fact_names]) fact_query.add([EqualsOperator("name", name) for name in fact_names])
query.add(fact_query)
if env != '*': if env != '*':
query.add(EqualsOperator("environment", env)) query.add(EqualsOperator("environment", env))
query.add(fact_query)
# get all the facts from PuppetDB
facts = puppetdb.facts(query=query) facts = puppetdb.facts(query=query)
fact_data = {}
for fact in facts: for fact in facts:
if fact.node not in fact_data: if fact.node not in fact_data:
fact_data[fact.node] = {} fact_data[fact.node] = {}
fact_data[fact.node][fact.name] = fact.value fact_data[fact.node][fact.name] = fact.value
return Response(stream_with_context( total = len(fact_data)
stream_template(
'inventory.html', return render_template(
headers=headers, 'inventory.json.tpl',
fact_names=fact_names, draw=draw,
total=total,
total_filtered=total,
fact_data=fact_data, fact_data=fact_data,
envs=envs, columns=fact_names)
current_env=env
)))
@app.route('/node/<node_name>/', @app.route('/node/<node_name>/',

View File

@@ -1,24 +1,21 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% block content %} {% block content %}
<div class="ui fluid icon input hide" style="margin-bottom:20px"> <table id="inventory_table" class='ui fixed compact very basic sortable table'>
<input autofocus="autofocus" class="filter-table" placeholder="Type here to filter...">
</div>
<table class='ui compact very basic sortable table'>
<thead> <thead>
<tr> <tr>
{% for head in headers %} {% for head in fact_headers %}
<th{% if loop.index == 1 %} class="default-sort"{% endif %}>{{head}}</th> <th>{{head}}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody class="searchable"> <tbody class="searchable">
{% for node, facts in fact_data.iteritems() %}
<tr>
{% for name in fact_names %}
<td><a href="{{url_for('node', env=current_env, node_name=node)}}">{{facts.get(name, 'undef')}}</a></td>
{% endfor %}
</tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
{% endblock content %} {% endblock content %}
{% block onload_script %}
{% macro extra_options(caller) %}
'serverSide': false,
{% endmacro %}
{{ macros.datatable_init(table_html_id="inventory_table", ajax_url=url_for('inventory_ajax', env=current_env), default_length=config.NORMAL_TABLE_COUNT, length_selector=config.TABLE_COUNT_SELECTOR, extra_options=extra_options) }}
{% endblock onload_script %}

View File

@@ -0,0 +1,23 @@
{%- import '_macros.html' as macros -%}
{
"draw": {{draw}},
"recordsTotal": {{total}},
"recordsFiltered": {{total_filtered}},
"data": [
{% for node in fact_data -%}
{%- if not loop.first %},{%- endif -%}
[
{%- for column in columns -%}
{%- if not loop.first %},{%- endif -%}
{%- if column in ['fqdn', 'hostname'] -%}
{% filter jsonprint %}<a href="{{ url_for('node', env=current_env, node_name=node) }}">{{ node }}</a>{% endfilter %}
{%- elif fact_data[node][column] -%}
{{ fact_data[node][column] | jsonprint }}
{%- else -%}
""
{%- endif -%}
{%- endfor -%}
]
{% endfor -%}
]
}