Skip to content

Commit

Permalink
Merge pull request #357 from redref/facts
Browse files Browse the repository at this point in the history
Facts revamp with datatables.
  • Loading branch information
mterzo authored Jun 10, 2017
2 parents 26825ae + 7cd8908 commit 38b2a2f
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 199 deletions.
185 changes: 118 additions & 67 deletions puppetboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pypuppetdb import connect
from pypuppetdb.QueryBuilder import *

from puppetboard.forms import (CatalogForm, QueryForm)
from puppetboard.forms import QueryForm
from puppetboard.utils import (
get_or_abort, yield_or_stop, get_db_version,
jsonprint, prettyprint
Expand Down Expand Up @@ -406,9 +406,9 @@ def inventory_ajax(env):
columns=fact_names)


@app.route('/node/<node_name>/',
@app.route('/node/<node_name>',
defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/node/<node_name>/')
@app.route('/<env>/node/<node_name>')
def node(env, node_name):
"""Display a dashboard for a node showing as much data as we have on that
node. This includes facts and reports but not Resources as that is too
Expand All @@ -427,21 +427,20 @@ def node(env, node_name):
query.add(EqualsOperator("certname", node_name))

node = get_or_abort(puppetdb.node, node_name)
facts = node.facts()

return render_template(
'node.html',
node=node,
facts=yield_or_stop(facts),
envs=envs,
current_env=env,
columns=REPORTS_COLUMNS[:2])


@app.route('/reports/',
@app.route('/reports',
defaults={'env': app.config['DEFAULT_ENVIRONMENT'],
'node_name': None})
@app.route('/<env>/reports/', defaults={'node_name': None})
@app.route('/reports/<node_name>/',
@app.route('/<env>/reports', defaults={'node_name': None})
@app.route('/reports/<node_name>',
defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/reports/<node_name>')
def reports(env, node_name):
Expand Down Expand Up @@ -638,106 +637,158 @@ def facts(env):
check_env(env, envs)
facts = []
order_by = '[{"field": "name", "order": "asc"}]'

if env == '*':
facts = get_or_abort(puppetdb.fact_names)
else:
query = ExtractOperator()
query.add_field(str('name'))
query.add_query(EqualsOperator("environment", env))
query.add_group_by(str("name"))

for names in get_or_abort(puppetdb._query,
'facts',
query=query,
order_by=order_by):
facts.append(names['name'])

facts_dict = collections.defaultdict(list)
facts = get_or_abort(puppetdb.fact_names)

facts_columns = [[]]
letter = None
letter_list = None
break_size = (len(facts) / 4) + 1
next_break = break_size
count = 0
for fact in facts:
letter = fact[0].upper()
letter_list = facts_dict[letter]
count += 1

if letter != fact[0].upper() or not letter:
if count > next_break:
# Create a new column
facts_columns.append([])
next_break += break_size
if letter_list:
facts_columns[-1].append(letter_list)
# Reset
letter = fact[0].upper()
letter_list = []

letter_list.append(fact)
facts_dict[letter] = letter_list
facts_columns[-1].append(letter_list)

sorted_facts_dict = sorted(facts_dict.items())
return render_template('facts.html',
facts_dict=sorted_facts_dict,
facts_len=(sum(map(len, facts_dict.values())) +
len(facts_dict) * 5),
facts_columns=facts_columns,
envs=envs,
current_env=env)


@app.route('/fact/<fact>', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/fact/<fact>')
def fact(env, fact):
"""Fetches the specific fact from PuppetDB and displays its value per
@app.route('/fact/<fact>',
defaults={'env': app.config['DEFAULT_ENVIRONMENT'], 'value': None})
@app.route('/<env>/fact/<fact>', defaults={'value': None})
@app.route('/fact/<fact>/<value>',
defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/fact/<fact>/<value>')
def fact(env, fact, value):
"""Fetches the specific fact(/value) from PuppetDB and displays per
node for which this fact is known.
:param env: Searches for facts in this environment
:type env: :obj:`string`
:param fact: Find all facts with this name
:type fact: :obj:`string`
:param value: Find all facts with this value
:type value: :obj:`string`
"""
envs = environments()
check_env(env, envs)

# we can only consume the generator once, lists can be doubly consumed
# om nom nom
render_graph = False
if fact in graph_facts:
if fact in graph_facts and not value:
render_graph = True

if env == '*':
query = None
else:
query = EqualsOperator("environment", env)

localfacts = [f for f in yield_or_stop(puppetdb.facts(
name=fact, query=query))]
return Response(stream_with_context(stream_template(
return render_template(
'fact.html',
name=fact,
fact=fact,
value=value,
render_graph=render_graph,
facts=localfacts,
envs=envs,
current_env=env)))
current_env=env)


@app.route('/fact/<fact>/<value>',
defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/fact/<fact>/<value>')
def fact_value(env, fact, value):
"""On asking for fact/value get all nodes with that fact.
@app.route('/fact/<fact>/json',
defaults={'env': app.config['DEFAULT_ENVIRONMENT'],
'node': None, 'value': None})
@app.route('/<env>/fact/<fact>/json', defaults={'node': None, 'value': None})
@app.route('/fact/<fact>/<value>/json',
defaults={'env': app.config['DEFAULT_ENVIRONMENT'], 'node': None})
@app.route('/<env>/fact/<fact>/<value>/json', defaults={'node': None})
@app.route('/node/<node>/facts/json',
defaults={'env': app.config['DEFAULT_ENVIRONMENT'],
'fact': None, 'value': None})
@app.route('/<env>/node/<node>/facts/json',
defaults={'fact': None, 'value': None})
def fact_ajax(env, node, fact, value):
"""Fetches the specific facts matching (node/fact/value) from PuppetDB and
return a JSON table
:param env: Searches for facts in this environment
:type env: :obj:`string`
:param node: Find all facts for this node
:type node: :obj:`string`
:param fact: Find all facts with this name
:type fact: :obj:`string`
:param value: Filter facts whose value is equal to this
:type value: :obj:`string`
"""
draw = int(request.args.get('draw', 0))

envs = environments()
check_env(env, envs)

if env == '*':
render_graph = False
if fact in graph_facts and not value and not node:
render_graph = True

query = AndOperator()
if node:
query.add(EqualsOperator("certname", node))

if env != '*':
query.add(EqualsOperator("environment", env))

if len(query.operations) == 0:
query = None
else:
query = EqualsOperator("environment", env)

facts = get_or_abort(puppetdb.facts,
name=fact,
value=value,
query=query)
localfacts = [f for f in yield_or_stop(facts)]
return render_template(
'fact.html',
# Generator needs to be converted (graph / total)
facts = [f for f in get_or_abort(
puppetdb.facts,
name=fact,
value=value,
facts=localfacts,
envs=envs,
current_env=env)
query=query)]

total = len(facts)

counts = {}
json = {
'draw': draw,
'recordsTotal': total,
'recordsFiltered': total,
'data': []}

for fact_h in facts:
line = []
if not fact:
line.append(fact_h.name)
if not node:
line.append('<a href="{0}">{1}</a>'.format(
url_for('node', env=env, node_name=fact_h.node),
fact_h.node))
if not value:
line.append('<a href="{0}">{1}</a>'.format(
url_for(
'fact', env=env, fact=fact_h.name, value=fact_h.value),
fact_h.value))

json['data'].append(line)

if render_graph:
if fact_h.value not in counts:
counts[fact_h.value] = 0
counts[fact_h.value] += 1

if render_graph:
json['chart'] = [
{"label": "{0}".format(k).replace('\n', ' '),
"value": counts[k]}
for k in sorted(counts, key=lambda k: counts[k], reverse=True)]

return jsonify(json)


@app.route('/query', methods=('GET', 'POST'),
Expand Down
6 changes: 0 additions & 6 deletions puppetboard/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,3 @@ class QueryForm(FlaskForm):
('pql', 'PQL'),
])
rawjson = BooleanField('Raw JSON')


class CatalogForm(FlaskForm):
"""The form used to compare the catalogs of different nodes."""
compare = HiddenField('compare')
against = SelectField('against')
50 changes: 2 additions & 48 deletions puppetboard/templates/_macros.html
Original file line number Diff line number Diff line change
@@ -1,51 +1,3 @@
{% macro facts_table(facts, current_env, autofocus=False, condensed=False, show_node=False, show_value=True, link_facts=False, margin_top=20, margin_bottom=20) -%}
<div class="ui fluid icon input hide" style="margin-bottom:20px">
<input {% if autofocus %} autofocus="autofocus" {% endif %} class="filter-table" placeholder="Type here to filter...">
</div>
<table class="ui very basic {% if condensed %}very{% endif%} compact sortable table" style="table-layout: fixed;">
<thead>
<tr>
{% if show_node %}
<th>Node</th>
{% else %}
<th class="default-sort">Fact</th>
{% endif %}
{% if show_value %}
<th>Value</th>
{% endif %}
</tr>
</thead>
<tbody class="searchable">
{% for fact in facts %}
<tr>
{% if show_node %}
<td><a href="{{url_for('node', env=current_env, node_name=fact.node)}}">{{fact.node}}</a></td>
{% else %}
<td><a href="{{url_for('fact', env=current_env, fact=fact.name)}}">{{fact.name}}</a></td>
{% endif %}
{% if show_value %}
<td style="word-wrap:break-word">
{% if link_facts %}
{% if fact.value is mapping %}
<a href="{{url_for('fact_value', env=current_env, fact=fact.name, value=fact.value)}}"><pre>{{fact.value|jsonprint}}</pre></a>
{% else %}
<a href="{{url_for('fact_value', env=current_env, fact=fact.name, value=fact.value)}}">{{fact.value}}</a>
{% endif %}
{% else %}
{% if fact.value is mapping %}
<pre>{{fact.value|jsonprint}}</pre>
{% else %}
{{fact.value}}
{% endif %}
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{%- endmacro %}

{% macro status_counts(caller, status, node_name, events, current_env, unreported_time=False, report_hash=False) -%}
<a class="ui {{status}} label status" href="{{url_for('report', env=current_env, node_name=node_name, report_id=report_hash)}}">{{ status|upper }}</a>
{% if status == 'unreported' %}
Expand Down Expand Up @@ -99,6 +51,8 @@
// Paging options
"lengthMenu": {{ length_selector }},
"pageLength": {{ default_length }},
// Search as regex (does not apply if serverSide)
"search": {"regex": true},
// Default sort
"order": [[ 0, "desc" ]],
// Custom options
Expand Down
Loading

0 comments on commit 38b2a2f

Please sign in to comment.