Merge branch 'master' into facts

This commit is contained in:
Mike Terzo
2017-06-10 03:40:13 -04:00
committed by GitHub
14 changed files with 161 additions and 75 deletions

11
hooks/pre_build Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
version=$(git describe HEAD --abbrev=4)
cat << EOF > puppetboard/version.py
#
# Puppetboard version module
#
__version__ = '${version}'
EOF

View File

@@ -27,6 +27,7 @@ from puppetboard.utils import (
from puppetboard.dailychart import get_daily_reports_chart
import werkzeug.exceptions as ex
import CommonMark
from . import __version__
@@ -322,29 +323,11 @@ def nodes(env):
current_env=env)))
@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.
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
def inventory_facts():
# a list of facts descriptions to go in table header
headers = []
# a list of inventory fact names
fact_names = []
# load the list of items/facts we want in our inventory
try:
@@ -362,33 +345,65 @@ def inventory(env):
headers.append(desc)
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()
fact_query = OrOperator()
fact_query.add([EqualsOperator("name", name) for name in fact_names])
query.add(fact_query)
if env != '*':
query.add(EqualsOperator("environment", env))
query.add(fact_query)
# get all the facts from PuppetDB
facts = puppetdb.facts(query=query)
fact_data = {}
for fact in facts:
if fact.node not in fact_data:
fact_data[fact.node] = {}
fact_data[fact.node][fact.name] = fact.value
return Response(stream_with_context(
stream_template(
'inventory.html',
headers=headers,
fact_names=fact_names,
fact_data=fact_data,
envs=envs,
current_env=env
)))
total = len(fact_data)
return render_template(
'inventory.json.tpl',
draw=draw,
total=total,
total_filtered=total,
fact_data=fact_data,
columns=fact_names)
@app.route('/node/<node_name>',
@@ -529,26 +544,17 @@ def reports_ajax(env, node_name):
reports_events = []
total = 0
report_event_counts = {}
# Create a map from the metrics data to what the templates
# use to express the data.
report_map = {
'success': 'successes',
'failure': 'failures',
'skipped': 'skips',
'noops': 'noop'
}
# Convert metrics to relational dict
metrics = {}
for report in reports_events:
if total is None:
total = puppetdb.total
report_counts = {'successes': 0, 'failures': 0, 'skips': 0}
for metrics in report.metrics:
if 'name' in metrics and metrics['name'] in report_map:
key_name = report_map[metrics['name']]
report_counts[key_name] = metrics['value']
report_event_counts[report.hash_] = report_counts
metrics[report.hash_] = {}
for m in report.metrics:
if m['category'] not in metrics[report.hash_]:
metrics[report.hash_][m['category']] = {}
metrics[report.hash_][m['category']][m['name']] = m['value']
if total is None:
total = 0
@@ -559,7 +565,7 @@ def reports_ajax(env, node_name):
total=total,
total_filtered=total,
reports=reports,
report_event_counts=report_event_counts,
metrics=metrics,
envs=envs,
current_env=env,
columns=REPORTS_COLUMNS[:max_col])
@@ -605,6 +611,8 @@ def report(env, node_name, report_id):
except StopIteration:
abort(404)
report.version = CommonMark.commonmark(report.version)
return render_template(
'report.html',
report=report,

View File

@@ -18,6 +18,11 @@ LOGLEVEL = 'info'
NORMAL_TABLE_COUNT = 100
LITTLE_TABLE_COUNT = 10
TABLE_COUNT_SELECTOR = [10, 20, 50, 100, 500]
DISPLAYED_METRICS = ['resources.total',
'events.failure',
'events.success',
'resources.skipped',
'events.noop']
OFFLINE_MODE = False
ENABLE_CATALOG = False
OVERVIEW_FILTER = None

View File

@@ -33,6 +33,13 @@ TABLE_COUNT_DEF = "10,20,50,100,500"
TABLE_COUNT_SELECTOR = [int(x) for x in os.getenv('TABLE_COUNT_SELECTOR',
TABLE_COUNT_DEF).split(',')]
DISP_METR_DEF = ','.join(['resources.total', 'events.failure',
'events.success', 'resources.skipped',
'events.noop'])
DISPLAYED_METRICS = [x.strip() for x in os.getenv('DISPLAYED_METRICS',
DISP_METR_DEF).split(',')]
OFFLINE_MODE = bool(os.getenv('OFFLINE_MODE', 'False').upper() == 'TRUE')
ENABLE_CATALOG = bool(os.getenv('ENABLE_CATALOG', 'False').upper() == 'TRUE')
OVERVIEW_FILTER = os.getenv('OVERVIEW_FILTER', None)
@@ -46,7 +53,6 @@ GRAPH_FACTS_DEFAULT = ','.join(['architecture', 'clientversion', 'domain',
GRAPH_FACTS = [x.strip() for x in os.getenv('GRAPH_FACTS',
GRAPH_FACTS_DEFAULT).split(',')]
GRAPH_TYPE = os.getenv('GRAPH_TYPE', 'pie')
# Tuples are hard to express as an environment variable, so here

View File

@@ -44,7 +44,7 @@ h1.ui.header.no-margin-bottom {
color: #AA4643;
}
.ui.label.failed {
.ui.label.failed, .ui.label.events.failure {
background-color: #AA4643;
}
@@ -52,7 +52,7 @@ h1.ui.header.no-margin-bottom {
color: #4572A7;
}
.ui.label.changed {
.ui.label.changed, .ui.label.events.success {
background-color: #4572A7;
}
@@ -68,10 +68,14 @@ h1.ui.header.no-margin-bottom {
color: #DB843D;
}
.ui.label.noop {
.ui.label.noop, .ui.label.events.noop {
background-color: #DB843D;
}
.ui.label.resources.total {
background-color: #989898;
}
.ui.label.unchanged {
background-color: #89A54E;
}
@@ -80,7 +84,7 @@ h1.ui.header.no-margin-bottom {
color: orange;
}
.ui.label.skipped {
.ui.label.skipped, .ui.label.resources.skipped {
background-color: orange;
}

View File

@@ -1,6 +1,6 @@
jQuery(function ($) {
function generateChart(el) {
var url = "/daily_reports_chart.json";
var url = "daily_reports_chart.json";
var certname = $(el).attr('data-certname');
if (typeof certname !== typeof undefined && certname !== false) {
url = url + "?certname=" + certname;

View File

@@ -9,6 +9,29 @@
{% endif %}
{%- endmacro %}
{% macro report_status(caller, status, node_name, metrics, 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' %}
<span class="ui label status"> {{ unreported_time|upper }} </span>
{% else %}
{% for metric in config.DISPLAYED_METRICS %}
{% set path = metric.split('.') %}
{% set title = ' '.join(path) %}
{% if metrics[path[0]] and metrics[path[0]][path[1]] %}
{% set value = metrics[path[0]][path[1]] %}
{% if value != 0 and value|int != value %}
{% set format_str = '%.2f' %}
{% else %}
{% set format_str = '%s' %}
{% endif %}
<span title="{{ title }}" class="ui small count label {{ title }}">{{ format_str|format(value) }}</span>
{% else %}
<span title="{{ title }}" class="ui small count label">0</span>
{% endif%}
{% endfor %}
{% endif %}
{%- endmacro %}
{% macro datatable_init(table_html_id, ajax_url, default_length, length_selector, extra_options=None) -%}
// Init datatable
$.fn.dataTable.ext.errMode = 'throw';

View File

@@ -1,24 +1,21 @@
{% extends 'layout.html' %}
{% import '_macros.html' as macros %}
{% 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 very basic sortable table'>
<table id="inventory_table" class='ui fixed compact very basic sortable table'>
<thead>
<tr>
{% for head in headers %}
<th{% if loop.index == 1 %} class="default-sort"{% endif %}>{{head}}</th>
{% for head in fact_headers %}
<th>{{head}}</th>
{% endfor %}
</tr>
</thead>
<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>
</table>
{% 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 -%}
]
}

View File

@@ -14,7 +14,7 @@
<tr>
<td><a href="{{url_for('node', env=current_env, node_name=report.node)}}">{{ report.node }}</a></td>
<td>
{{report.version}}
{{report.version|safe}}
</td>
<td rel="utctimestamp">
{{report.start}}

View File

@@ -15,7 +15,7 @@
"<span rel=\"utctimestamp\">{{ report[column.attr] }}</span>"
{%- elif column.type == 'status' -%}
{% filter jsonprint -%}
{{ macros.status_counts(status=report.status, node_name=report.node, events=report_event_counts[report.hash_], report_hash=report.hash_, current_env=current_env) }}
{{ macros.report_status(status=report.status, node_name=report.node, metrics=metrics[report.hash_], report_hash=report.hash_, current_env=current_env) }}
{%- endfilter %}
{%- elif column.type == 'node' -%}
{% filter jsonprint %}<a href="{{url_for('node', env=current_env, node_name=report.node)}}">{{ report.node }}</a>{% endfilter %}

View File

@@ -7,3 +7,4 @@ Werkzeug >=0.12.1
itsdangerous >=0.23
pypuppetdb >=0.3.2
requests >=2.13.0
CommonMark==0.7.2

View File

@@ -116,3 +116,11 @@ def test_env_table_selector(cleanUpEnv):
os.environ['TABLE_COUNT_SELECTOR'] = '5,15,25'
reload(docker_settings)
assert [5, 15, 25] == docker_settings.TABLE_COUNT_SELECTOR
def test_env_column_options(cleanUpEnv):
os.environ['DISPLAYED_METRICS'] = 'resources.total, events.failure'
reload(docker_settings)
assert ['resources.total',
'events.failure'] == docker_settings.DISPLAYED_METRICS